diff -Nru ubuntu-advantage-tools-31.2.3~16.04/.coveragerc ubuntu-advantage-tools-32~16.04/.coveragerc --- ubuntu-advantage-tools-31.2.3~16.04/.coveragerc 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/.coveragerc 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,4 @@ +[paths] +source = + uaclient/ + /usr/lib/python3/dist-packages/uaclient/ diff -Nru ubuntu-advantage-tools-31.2.3~16.04/.github/actions/create-issue/package-lock.json ubuntu-advantage-tools-32~16.04/.github/actions/create-issue/package-lock.json --- ubuntu-advantage-tools-31.2.3~16.04/.github/actions/create-issue/package-lock.json 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/.github/actions/create-issue/package-lock.json 2024-04-23 13:37:02.000000000 +0000 @@ -205,9 +205,9 @@ } }, "node_modules/undici": { - "version": "5.27.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.2.tgz", - "integrity": "sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==", + "version": "5.28.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", + "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==", "dependencies": { "@fastify/busboy": "^2.0.0" }, @@ -388,9 +388,9 @@ "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" }, "undici": { - "version": "5.27.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.2.tgz", - "integrity": "sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==", + "version": "5.28.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", + "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==", "requires": { "@fastify/busboy": "^2.0.0" } diff -Nru ubuntu-advantage-tools-31.2.3~16.04/.github/workflows/ci-integration.yaml ubuntu-advantage-tools-32~16.04/.github/workflows/ci-integration.yaml --- ubuntu-advantage-tools-31.2.3~16.04/.github/workflows/ci-integration.yaml 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/.github/workflows/ci-integration.yaml 2024-04-23 13:37:02.000000000 +0000 @@ -27,7 +27,7 @@ runs-on: ubuntu-22.04 strategy: matrix: - release: ['xenial', 'bionic', 'focal', 'jammy', 'mantic'] # , 'noble'] TODO flake8 is broken in noble as of Feb 21 2024. Add back once python3-flake8 >6 (currently in -proposed) gets into noble + release: ['xenial', 'bionic', 'focal', 'jammy', 'mantic', 'noble'] steps: - name: Prepare build tools env: @@ -47,7 +47,12 @@ run: | gbp dch --ignore-branch --snapshot --distribution=${{ matrix.release }} dch --local=~${{ matrix.release }} "" - sg sbuild -c "mk-sbuild --skip-proposed ${{ matrix.release }}" + if [ \"${{ matrix.release }}\" = \"noble\" ]; then # TODO update this for the new devel after noble is released + SKIP_PROPOSED="" + else + SKIP_PROPOSED="--skip-proposed" + fi + sg sbuild -c "mk-sbuild $SKIP_PROPOSED ${{ matrix.release }}" sg sbuild -c "sbuild --dist='${{ matrix.release }}' --resolve-alternatives --no-clean-source --nolog --verbose --no-run-lintian --build-dir='${{ runner.temp }}'" mv ../*.deb '${{ runner.temp }}' # Workaround for Debbug: #990734, drop in Jammy - name: Archive debs as artifacts @@ -65,7 +70,7 @@ # as much information as possible from them. fail-fast: false matrix: - release: ['bionic', 'focal', 'jammy', 'mantic'] + release: ['bionic', 'focal', 'jammy', 'mantic', 'noble'] platform: ['lxd-container'] host_os: ['ubuntu-22.04'] include: @@ -144,7 +149,7 @@ sh -c 'printf "%s\n" "$SSH_PRIVATE_KEY" > ~/.ssh/cloudinit_id_rsa' sh -c 'printf "%s\n" "$SSH_PUBLIC_KEY" > ~/.ssh/cloudinit_id_rsa.pub' - sg lxd -c "tox -e behave -- -D machine_types=${{ matrix.platform }} -D releases=${{ matrix.release }} --tags=-slow --tags=-upgrade" + sg lxd -c "tox -e behave -- -D machine_types=${{ matrix.platform }} -D releases=${{ matrix.release }} --tags=-slow --tags=-upgrade --tags=-no_gh --tags=-vpn" - name: Archive test artifacts if: always() uses: actions/upload-artifact@v3 diff -Nru ubuntu-advantage-tools-31.2.3~16.04/.github/workflows/custom_pr_checks.yaml ubuntu-advantage-tools-32~16.04/.github/workflows/custom_pr_checks.yaml --- ubuntu-advantage-tools-31.2.3~16.04/.github/workflows/custom_pr_checks.yaml 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/.github/workflows/custom_pr_checks.yaml 2024-04-23 13:37:02.000000000 +0000 @@ -11,6 +11,7 @@ - edited branches: - main + - next-* jobs: bug-refs: diff -Nru ubuntu-advantage-tools-31.2.3~16.04/.pre-commit-config.yaml ubuntu-advantage-tools-32~16.04/.pre-commit-config.yaml --- ubuntu-advantage-tools-31.2.3~16.04/.pre-commit-config.yaml 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/.pre-commit-config.yaml 2024-04-23 13:37:02.000000000 +0000 @@ -1,12 +1,16 @@ repos: - repo: https://github.com/ambv/black - rev: 22.3.0 # Also stored in dev-requirements.txt; update both together! + rev: 24.3.0 # Also stored in dev-requirements.txt; update both together! hooks: - id: black - repo: https://github.com/pycqa/isort rev: 5.12.0 # Also stored in dev-requirements.txt; update both together! hooks: - id: isort + - repo: https://github.com/ducminh-phan/reformat-gherkin + rev: v3.0.1 # Also stored in dev-requirements.txt; update both together! + hooks: + - id: reformat-gherkin - repo: https://github.com/shellcheck-py/shellcheck-py rev: v0.9.0.6 # Also stored in dev-requirements.txt; update both together! hooks: diff -Nru ubuntu-advantage-tools-31.2.3~16.04/apport/source_ubuntu-advantage-tools.py ubuntu-advantage-tools-32~16.04/apport/source_ubuntu-advantage-tools.py --- ubuntu-advantage-tools-31.2.3~16.04/apport/source_ubuntu-advantage-tools.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/apport/source_ubuntu-advantage-tools.py 2024-04-23 13:37:02.000000000 +0000 @@ -17,8 +17,8 @@ auto_include_log_files = { "cloud-id.txt", "cloud-id.txt-error", - "ua-status.json", - "ua-status.json-error", + "pro-status.json", + "pro-status.json-error", "livepatch-status.txt", "livepatch-status.txt-error", "pro-journal.txt", diff -Nru ubuntu-advantage-tools-31.2.3~16.04/apt-hook/json-hook.cc ubuntu-advantage-tools-32~16.04/apt-hook/json-hook.cc --- ubuntu-advantage-tools-31.2.3~16.04/apt-hook/json-hook.cc 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/apt-hook/json-hook.cc 2024-04-23 13:37:02.000000000 +0000 @@ -147,6 +147,92 @@ return true; } +bool version_from_origin(json_object *version, std::string from_origin) { + bool has_key = false; + json_object *origins; + has_key = json_object_object_get_ex(version, "origins", &origins); + if (!has_key) { + return false; + } + int64_t origins_length = json_object_array_length(origins); + + for (int64_t i = 0; i < origins_length; i++) { + json_object *origin = json_object_array_get_idx(origins, i); + + json_object *tmp; + has_key = json_object_object_get_ex(origin, "origin", &tmp); + if (!has_key) { + continue; + } + std::string origin_origin(json_object_get_string(tmp)); + + if (origin_origin == from_origin) { + return true; + } + } + + return false; +} + +bool collect_pro_packages_from_pre_prompt_json(json_object *pre_prompt, std::vector *expired_packages) { + bool has_key = false; + + json_object *packages; + has_key = json_object_object_get_ex(pre_prompt, "packages", &packages); + if (!has_key) { + return false; + } + int64_t packages_length = json_object_array_length(packages); + + for (int64_t i = 0; i < packages_length; i++) { + json_object *package = json_object_array_get_idx(packages, i); + + json_object *tmp; + has_key = json_object_object_get_ex(package, "mode", &tmp); + if (!has_key) { + continue; + } + std::string package_mode(json_object_get_string(tmp)); + + has_key = json_object_object_get_ex(package, "name", &tmp); + if (!has_key) { + continue; + } + std::string package_name(json_object_get_string(tmp)); + + if (package_mode == "upgrade") { + json_object *versions; + has_key = json_object_object_get_ex(package, "versions", &versions); + if (!has_key) { + continue; + } + + json_object *install; + has_key = json_object_object_get_ex(versions, "install", &install); + if (!has_key) { + continue; + } + + if ( + version_from_origin(install, "UbuntuESM") + || version_from_origin(install, "UbuntuESMApps") + || version_from_origin(install, "UbuntuCC") + || version_from_origin(install, "UbuntuCIS") + || version_from_origin(install, "UbuntuFIPS") + || version_from_origin(install, "UbuntuFIPSUpdates") + || version_from_origin(install, "UbuntuFIPSPreview") + || version_from_origin(install, "UbuntuRealtimeKernel") + || version_from_origin(install, "UbuntuROS") + || version_from_origin(install, "UbuntuROSUpdates") + ) { + expired_packages->push_back(package_name); + } + } + } + + return true; +} + #define MAX_COUNT_MESSAGE_LEN 256 std::string create_count_message(security_package_counts &counts) { char buf[MAX_COUNT_MESSAGE_LEN] = {0}; @@ -477,8 +563,21 @@ return; } -void print_esm_packages(ESMType esm_type, std::vector package_names) { +void print_package_names(std::vector package_names) { + std::string curr_line = " "; + for (std::string &name : package_names) { + if ((curr_line.length() + 1 + name.length()) >= 79) { + std::cout << curr_line << std::endl; + curr_line = " "; + } + curr_line = curr_line + " " + name; + } + if (curr_line.length() > 1) { + std::cout << curr_line << std::endl; + } +} +void print_esm_packages(ESMType esm_type, std::vector package_names) { if (esm_type == APPS) { printf( ngettext( @@ -499,21 +598,29 @@ printf("\n"); } - std::string curr_line = " "; - for (std::string &name : package_names) { - if ((curr_line.length() + 1 + name.length()) >= 79) { - std::cout << curr_line << std::endl; - curr_line = " "; - } - curr_line = curr_line + " " + name; - } - if (curr_line.length() > 1) { - std::cout << curr_line << std::endl; - } + print_package_names(package_names); print_learn_more_with_context(); } +void print_expired_pro_packages(std::vector package_names) { + printf( + gettext( + "The following packages will fail to download because your Ubuntu Pro subscription has expired" + ) + ); + printf("\n"); + + print_package_names(package_names); + + printf( + gettext( + "Renew your subscription or `sudo pro detach` to remove these errors" + ) + ); + printf("\n"); +} + int run() { char *fd_c_str = getenv("APT_HOOK_SOCKET"); @@ -606,6 +713,16 @@ std::cout << apt_news_file.rdbuf(); apt_news_file.close(); } + + // Expired explanation + std::ifstream expired_notice("/var/lib/ubuntu-advantage/notices/5-contract_expired"); + if (expired_notice.is_open()) { + std::vector expired_packages; + bool success = collect_pro_packages_from_pre_prompt_json(hook_req.params, &expired_packages); + if (success && expired_packages.size() > 0) { + print_expired_pro_packages(expired_packages); + } + } } json_object_put(hook_req.root_msg); diff -Nru ubuntu-advantage-tools-31.2.3~16.04/debian/apparmor/ubuntu_pro_apt_news.jinja2 ubuntu-advantage-tools-32~16.04/debian/apparmor/ubuntu_pro_apt_news.jinja2 --- ubuntu-advantage-tools-31.2.3~16.04/debian/apparmor/ubuntu_pro_apt_news.jinja2 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/debian/apparmor/ubuntu_pro_apt_news.jinja2 2024-05-10 17:07:05.000000000 +0000 @@ -3,6 +3,9 @@ {% endif %} include +# attach_disconnected is needed here because this service runs with systemd's +# PrivateTmp=true + profile ubuntu_pro_apt_news flags=(attach_disconnected) { include include @@ -14,6 +17,8 @@ capability setgid, capability setuid, capability dac_read_search, + # GH: 3079 + capability dac_override, /etc/apt/** r, /etc/default/apport r, @@ -37,6 +42,7 @@ /tmp/** r, owner @{PROC}/@{pid}/fd/ r, + @{PROC}/@{pid}/status r, @{PROC}/@{pid}/cgroup r, {% if ubuntu_codename in ["bionic", "xenial"] %} # see https://bugs.python.org/issue40501 @@ -61,4 +67,7 @@ /var/cache/apt/archives/partial/ rw, /var/cache/apt/archives/partial/* rw, {% endif %} + + # Site-specific additions and overrides. See local/README for details. + #include } diff -Nru ubuntu-advantage-tools-31.2.3~16.04/debian/apparmor/ubuntu_pro_esm_cache.jinja2 ubuntu-advantage-tools-32~16.04/debian/apparmor/ubuntu_pro_esm_cache.jinja2 --- ubuntu-advantage-tools-31.2.3~16.04/debian/apparmor/ubuntu_pro_esm_cache.jinja2 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/debian/apparmor/ubuntu_pro_esm_cache.jinja2 2024-05-10 17:07:05.000000000 +0000 @@ -0,0 +1,305 @@ +{% if ubuntu_codename not in ["xenial", "bionic", "focal"] %} +abi , +{% endif %} +include + +# attach_disconnected is needed in all profiles defined here because this +# service runs with systemd's PrivateTmp=true + +profile ubuntu_pro_esm_cache flags=(attach_disconnected) { + include + include + include + include + include + + capability chown, + capability dac_override, + capability dac_read_search, + capability fowner, + capability kill, + capability setgid, + capability setuid, + + signal send set=int peer=ubuntu_pro_esm_cache//apt_methods, + signal send set=int peer=ubuntu_pro_esm_cache//apt_methods_gpgv, + + /etc/apt/** r, + /etc/machine-id r, + /etc/ubuntu-advantage/uaclient.conf r, + + /run/ubuntu-advantage/ rw, + /run/ubuntu-advantage/** rw, + + /run/systemd/container/ r, + /run/systemd/container/** r, + + /usr/bin/apt mrix, + /usr/bin/apt-cache mrix, + /usr/bin/ischroot mrix, + /usr/bin/python3.{1,}[0-9] mrix, + /usr/bin/uname mrix, + + /usr/bin/cloud-id Cx -> cloud_id, + # in bionic, it's /bin/ps, so let's match both + /{,usr/}bin/ps Cx -> ps, + /usr/bin/systemd-detect-virt Px -> ubuntu_pro_esm_cache_systemd_detect_virt, + /usr/bin/dpkg Cx -> dpkg, + /usr/bin/ubuntu-distro-info Cx -> ubuntu_distro_info, + /usr/lib/apt/methods/gpgv Cx -> apt_methods_gpgv, + /usr/lib/apt/methods/http Cx -> apt_methods, + /usr/lib/apt/methods/https Cx -> apt_methods, + /usr/lib/apt/methods/store Cx -> apt_methods, + # when there is no status.json cached, esm-cache.service will invoke "snap status" + /usr/bin/snap PUx, + + /usr/share/dpkg/** r, + /usr/share/keyrings/* r, + + /var/cache/apt/** rw, + + /var/lib/apt/** r, + /var/lib/dpkg/** r, + /var/lib/ubuntu-advantage/** rwk, + + /var/log/ubuntu-advantage.log rw, + + @{PROC}/@{pid}/fd/ r, + @{PROC}/1/cgroup r, + @{PROC}/version_signature r, + @{PROC}/@{pid}/mountinfo r, + @{PROC}/@{pid}/status r, + @{PROC}/@{pid}/stat r, + @{PROC}/sys/kernel/osrelease r, + +{% if ubuntu_codename in ["bionic", "xenial"] %} + # see https://bugs.python.org/issue40501 + /sbin/ldconfig rix, + /sbin/ldconfig.real rix, + @{PROC}/@{pid}/mounts r, + /usr/bin/@{multiarch}-gcc-* rix, + /usr/bin/@{multiarch}-ld.bfd rix, + /usr/lib/gcc/@{multiarch}/*/collect2 rix, + /usr/bin/@{multiarch}-objdump rix, +{% endif %} + + profile ps flags=(attach_disconnected) { + include + include + + capability sys_ptrace, + + # GH: #3079 + capability dac_read_search, + capability dac_override, + + ptrace read, + + # in bionic, it's /bin/ps, so let's match both + /{,usr/}bin/ps mrix, + + /dev/tty r, + + @{PROC}/ r, + @{PROC}/@{pid}/** r, + @{PROC}/uptime r, + @{PROC}/sys/kernel/** r, + # GH: #3079 + @{PROC}/tty/drivers r, + /sys/devices/system/node/ r, + /sys/devices/system/node/** r, + } + + profile cloud_id flags=(attach_disconnected) { + include + include + include + + ptrace read peer=unconfined, + + /etc/cloud/** r, + /etc/apt/** r, + /etc/apport/** r, + /etc/ssl/openssl.cnf r, + + @{PROC}/@{pid}/fd/ r, + @{PROC}/cmdline r, + @{PROC}/1/environ r, + @{PROC}/1/cmdline r, + @{PROC}/@{pid}/status r, + + /run/cloud-init/** r, + + /usr/bin/ r, + /usr/bin/cloud-id r, + /usr/bin/python3.{1,}[0-9] mrix, + /usr/bin/uname mrix, + + /usr/share/dpkg/** r, + + # workarounds for + # https://gitlab.com/apparmor/apparmor/-/issues/346 + /usr/bin/systemctl Px -> ubuntu_pro_esm_cache_systemctl, + /usr/bin/systemd-detect-virt Px -> ubuntu_pro_esm_cache_systemd_detect_virt, + + /var/lib/cloud/** r, + +{% if ubuntu_codename in ["bionic", "xenial"] %} + # see https://bugs.python.org/issue40501 + /sbin/ldconfig rix, + /sbin/ldconfig.real rix, + @{PROC}/@{pid}/mounts r, + /usr/bin/@{multiarch}-gcc-* rix, + /usr/bin/@{multiarch}-ld.bfd rix, + /usr/lib/gcc/@{multiarch}/*/collect2 rix, + /usr/bin/@{multiarch}-objdump rix, + + /etc/lsb-release r, + @{PROC}/cmdline r, + /bin/dash mrix, + /bin/uname mrix, +{% endif %} + + } + + profile dpkg flags=(attach_disconnected) { + include + + capability setgid, + + /etc/dpkg/** r, + + /usr/bin/dpkg mr, + + } + + profile ubuntu_distro_info flags=(attach_disconnected) { + include + + /usr/bin/ubuntu-distro-info mr, + + /usr/share/distro-info/** r, + + } + + profile apt_methods flags=(attach_disconnected) { + include + include + include + include + + capability setgid, + capability setuid, + + network inet stream, + network inet6 stream, + + signal receive set=int peer=ubuntu_pro_esm_cache, + + / r, + /etc/dpkg/** r, + + /usr/lib/apt/methods/gpgv mr, + /usr/lib/apt/methods/http mr, + /usr/lib/apt/methods/https mr, + /usr/lib/apt/methods/store mr, + + /usr/share/dpkg/** r, + + /var/lib/ubuntu-advantage/apt-esm/** rwk, + + @{PROC}/@{pid}/cgroup r, + @{PROC}/@{pid}/fd/ r, + + } + + profile apt_methods_gpgv flags=(attach_disconnected) { + include + include + include + include + + capability setgid, + capability setuid, + + signal receive set=int peer=ubuntu_pro_esm_cache, + + / r, + /etc/dpkg/** r, + + # there are just too many shell script tools that are called, like head, + # tail, cut, sed, etc + /{,usr/}bin/* mrix, + + /usr/lib/apt/methods/gpgv mr, + + /usr/share/dpkg/** r, + /usr/share/keyrings/* r, + + /var/lib/ubuntu-advantage/apt-esm/** r, + + @{PROC}/@{pid}/fd/ r, + + # apt-config command needs these + # Note: observed only in xenial tests, but makes sense for all releases + /etc/apt/** r, + /var/lib/apt/** r, + + } + + # Site-specific additions and overrides. See local/README for details. + #include +} + + # these profiles were initially subprofiles of cloud-id, but: + # a) that crashes the kernel + # https://gitlab.com/apparmor/apparmor/-/issues/346 + # b) <= bionic doesn't like the // or - chars in profile names + # https://gitlab.com/apparmor/apparmor/-/commit/99755daafb8cfde4df542b66f656597a482129ac + + profile ubuntu_pro_esm_cache_systemctl flags=(attach_disconnected) { + include + + capability net_admin, + capability sys_ptrace, + + ptrace read peer=unconfined, + +{% if ubuntu_codename in ["noble"] %} + unix bind addr=@*/bus/systemctl/{,system}, +{% endif %} + + /usr/bin/systemctl mr, + + /run/systemd/private rw, + /run/systemd/** r, + + @{PROC}/cmdline r, + @{PROC}/1/cmdline r, + @{PROC}/@{pid}/stat r, + @{PROC}/sys/kernel/osrelease r, + } + + profile ubuntu_pro_esm_cache_systemd_detect_virt flags=(attach_disconnected) { + include + + capability sys_ptrace, + + ptrace read peer=unconfined, +{% if ubuntu_codename ["xenial"] %} + ptrace trace peer=unconfined, +{% endif %} + /usr/bin/systemd-detect-virt mr, + + /run/systemd/** r, + + /sys/devices/virtual/** r, + @{PROC}/@{pid}/status r, + @{PROC}/@{pid}/stat r, + @{PROC}/1/environ r, + @{PROC}/1/sched r, + @{PROC}/cmdline r, + @{PROC}/1/cmdline r, + @{PROC}/sys/kernel/osrelease r, + + } diff -Nru ubuntu-advantage-tools-31.2.3~16.04/debian/changelog ubuntu-advantage-tools-32~16.04/debian/changelog --- ubuntu-advantage-tools-31.2.3~16.04/debian/changelog 2024-04-05 13:08:47.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/debian/changelog 2024-05-10 17:19:11.000000000 +0000 @@ -1,8 +1,46 @@ -ubuntu-advantage-tools (31.2.3~16.04) xenial; urgency=medium +ubuntu-advantage-tools (32~16.04) xenial; urgency=medium - * Backport new upstream release to xenial (LP: #2059952) + * Backport new upstream release to xenial (LP: #2060732) - -- Lucas Moura Fri, 05 Apr 2024 10:08:47 -0300 + -- Grant Orndorff Fri, 10 May 2024 12:19:11 -0500 + +ubuntu-advantage-tools (32) oracular; urgency=medium + + * d/postinst: ensure migrations happen in correct package postinst (GH: #2982) + * d/apparmor: introduce new ubuntu_pro_esm_cache apparmor policy + * New upstream release 32 (LP: #2060732) + - api: + + u.pro.attach.token.full_token_attach.v1: add support for attach + with token + + u.pro.services.disable.v1: add support for disable operation + + u.pro.services.enable.v1: add support for enable operation + + u.pro.detach.v1: add support for detach operation + + u.pro.status.is_attached.v1: add extra fields to API response + + u.pro.services.dependencies.v1: add support for service dependencies + + u.pro.security.fix.*.plan.v1: update ESM cache during plan API + if needed + - apt_news: add architectures and packages selectors filters for apt news + - cli: + + improved cli/log message for unexpected errors (GH: #2600) + + properly handle setting empty config values (GH: #2925) + - cloud-init: support ubuntu_pro user-data + - collect-logs: update default output file to pro_logs.tar.gz (LP: #2033313) + - config: create public and private config (GH: #2809) + - entitlements: + + update logic that checks if a service is enabled (LP: #2031192) + - fips: warn/confirm with user if enabling fips downgrades the kernel + - fix: warn users if ESM cache cannot be updated (GH: #2841) + - logging: + + use journald logging for all systemd services + + add redundancy to secret redaction + - messaging: + + add consistent messaging for end of contract state + + make explicit that unattached enable/disable is a noop (GH: #2487) + + make explicit that disabling a disabled service is a noop + + make explicit that enabling an enabled service is a noop + - notices: filter unreadable notices when listing notices (GH: #2898) + + -- Lucas Moura Tue, 09 Apr 2024 17:33:36 -0300 ubuntu-advantage-tools (31.2.3) noble; urgency=medium diff -Nru ubuntu-advantage-tools-31.2.3~16.04/debian/clean ubuntu-advantage-tools-32~16.04/debian/clean --- ubuntu-advantage-tools-31.2.3~16.04/debian/clean 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/debian/clean 2024-04-23 13:37:02.000000000 +0000 @@ -1 +1,3 @@ debian/apparmor/ubuntu_pro_apt_news +debian/apparmor/ubuntu_pro_esm_cache +debian/apparmor/local/ diff -Nru ubuntu-advantage-tools-31.2.3~16.04/debian/po/pt_BR.po ubuntu-advantage-tools-32~16.04/debian/po/pt_BR.po --- ubuntu-advantage-tools-31.2.3~16.04/debian/po/pt_BR.po 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/debian/po/pt_BR.po 2024-05-10 17:07:05.000000000 +0000 @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-19 16:13-0500\n" +"POT-Creation-Date: 2024-04-26 14:29-0300\n" "PO-Revision-Date: 2023-09-25 12:29-0400\n" "Last-Translator: Lucas Moura \n" "Language-Team: Brazilian Portuguese 1);\n" -#: ../../apt-hook/json-hook.cc:159 +#: ../../apt-hook/json-hook.cc:245 msgid "1 standard LTS security update" msgstr "1 atualização de segurança de LTS" -#: ../../apt-hook/json-hook.cc:164 +#: ../../apt-hook/json-hook.cc:250 #, c-format msgid "%lu standard LTS security updates" msgstr "%lu atualizações de segurança de LTS" -#: ../../apt-hook/json-hook.cc:171 +#: ../../apt-hook/json-hook.cc:257 msgid "1 esm-infra security update" msgstr "1 atualização de segurança do esm-infra" -#: ../../apt-hook/json-hook.cc:173 +#: ../../apt-hook/json-hook.cc:259 msgid "1 standard LTS security update and 1 esm-infra security update" msgstr "" "1 atualização de segurança de LTS e 1 atualização de segurança do esm-infra" -#: ../../apt-hook/json-hook.cc:178 +#: ../../apt-hook/json-hook.cc:264 #, c-format msgid "%lu standard LTS security updates and 1 esm-infra security update" msgstr "" "%lu atualizações de segurança de LTS e 1 atualização de segurança do esm-" "infra" -#: ../../apt-hook/json-hook.cc:188 +#: ../../apt-hook/json-hook.cc:274 #, c-format msgid "%lu esm-infra security updates" msgstr "%lu atualizações de segurança do esm-infra" -#: ../../apt-hook/json-hook.cc:196 +#: ../../apt-hook/json-hook.cc:282 #, c-format msgid "1 standard LTS security update and %lu esm-infra security updates" msgstr "" "1 atualização de segurança de LTS e %lu atualizações de segurança do esm-" "infra" -#: ../../apt-hook/json-hook.cc:204 +#: ../../apt-hook/json-hook.cc:290 #, c-format msgid "%lu standard LTS security updates and %lu esm-infra security updates" msgstr "" "%lu atualizações de segurança de LTS e %lu atualizações de segurança do esm-" "infra" -#: ../../apt-hook/json-hook.cc:214 +#: ../../apt-hook/json-hook.cc:300 msgid "1 esm-apps security update" msgstr "1 atualização de segurança do esm-apps" -#: ../../apt-hook/json-hook.cc:216 +#: ../../apt-hook/json-hook.cc:302 msgid "1 standard LTS security update and 1 esm-apps security update" msgstr "" "1 atualização de segurança de LTS e 1 atualização de segurança do esm-apps" -#: ../../apt-hook/json-hook.cc:221 +#: ../../apt-hook/json-hook.cc:307 #, c-format msgid "%lu standard LTS security updates and 1 esm-apps security update" msgstr "" "%lu atualizações de segurança de LTS e 1 atualização de segurança do esm-apps" -#: ../../apt-hook/json-hook.cc:228 +#: ../../apt-hook/json-hook.cc:314 msgid "1 esm-infra security update and 1 esm-apps security update" msgstr "" "1 atualização de segurança do esm-infra e 1 atualização de segurança do esm-" "apps" -#: ../../apt-hook/json-hook.cc:230 +#: ../../apt-hook/json-hook.cc:316 msgid "" "1 standard LTS security update, 1 esm-infra security update and 1 esm-apps " "security update" @@ -91,7 +91,7 @@ "1 atualização de segurança de LTS, 1 atualização de segurança do esm-infra e " "1 atualização de segurança do esm-apps" -#: ../../apt-hook/json-hook.cc:235 +#: ../../apt-hook/json-hook.cc:321 #, c-format msgid "" "%lu standard LTS security updates, 1 esm-infra security update and 1 esm-" @@ -100,14 +100,14 @@ "%lu atualizações de segurança de LTS, 1 atualização de segurança do esm-" "infra e 1 atualização de segurança do esm-apps" -#: ../../apt-hook/json-hook.cc:245 +#: ../../apt-hook/json-hook.cc:331 #, c-format msgid "%lu esm-infra security updates and 1 esm-apps security update" msgstr "" "%lu atualizações de segurança do esm-infra e 1 atualização de segurança do " "esm-apps" -#: ../../apt-hook/json-hook.cc:253 +#: ../../apt-hook/json-hook.cc:339 #, c-format msgid "" "1 standard LTS security update, %lu esm-infra security updates and 1 esm-" @@ -116,7 +116,7 @@ "1 atualização de segurança de LTS, %lu atualizações de segurança do esm-" "infra e 1 atualização de segurança do esm-apps" -#: ../../apt-hook/json-hook.cc:261 +#: ../../apt-hook/json-hook.cc:347 #, c-format msgid "" "%lu standard LTS security updates, %lu esm-infra security updates and 1 esm-" @@ -125,32 +125,32 @@ "%lu atualizações de segurança de LTS, %lu atualizações de segurança do esm-" "infra e 1 atualização de segurança do esm-apps" -#: ../../apt-hook/json-hook.cc:274 +#: ../../apt-hook/json-hook.cc:360 #, c-format msgid "%lu esm-apps security updates" msgstr "%lu atualizações de segurança do esm-apps" -#: ../../apt-hook/json-hook.cc:282 +#: ../../apt-hook/json-hook.cc:368 #, c-format msgid "1 standard LTS security update and %lu esm-apps security updates" msgstr "" "1 atualização de segurança de LTS e %lu atualizações de segurança do esm-apps" -#: ../../apt-hook/json-hook.cc:290 +#: ../../apt-hook/json-hook.cc:376 #, c-format msgid "%lu standard LTS security updates and %lu esm-apps security updates" msgstr "" "%lu atualizações de segurança de LTS and %lu atualizações de segurança do " "esm-apps" -#: ../../apt-hook/json-hook.cc:301 +#: ../../apt-hook/json-hook.cc:387 #, c-format msgid "1 esm-infra security update and %lu esm-apps security updates" msgstr "" "1 atualização de segurança do esm-infra e %lu atualizações de segurança do " "esm-apps" -#: ../../apt-hook/json-hook.cc:309 +#: ../../apt-hook/json-hook.cc:395 #, c-format msgid "" "1 standard LTS security update, 1 esm-infra security update and %lu esm-apps " @@ -159,7 +159,7 @@ "1 atualização de segurança de LTS, 1 atualização de segurança do esm-infra e " "%lu atualizações de segurança do esm-apps" -#: ../../apt-hook/json-hook.cc:317 +#: ../../apt-hook/json-hook.cc:403 #, c-format msgid "" "%lu standard LTS security updates, 1 esm-infra security update and %lu esm-" @@ -168,14 +168,14 @@ "%lu atualizações de segurança de LTS, 1 atualização de segurança de esm-" "infra e%lu atualizações de segurança do esm-apps" -#: ../../apt-hook/json-hook.cc:328 +#: ../../apt-hook/json-hook.cc:414 #, c-format msgid "%lu esm-infra security updates and %lu esm-apps security updates" msgstr "" "%lu atualizações de segurança do esm-infra e %lu atualizações de segurança " "do esm-apps" -#: ../../apt-hook/json-hook.cc:337 +#: ../../apt-hook/json-hook.cc:423 #, c-format msgid "" "1 standard LTS security update, %lu esm-infra security updates and %lu esm-" @@ -184,7 +184,7 @@ "1 atualização de segurança de LTS, %lu atualizações de segurança do esm-" "infra e%lu atualizações de segurança do esm-apps" -#: ../../apt-hook/json-hook.cc:346 +#: ../../apt-hook/json-hook.cc:432 #, c-format msgid "" "%lu standard LTS security updates, %lu esm-infra security updates and %lu " @@ -193,47 +193,47 @@ "%lu atualizações de segurança de LTS, %lu atualizações de segurança do esm-" "infra e %lu atualizações de segurança do esm-apps" -#: ../../apt-hook/json-hook.cc:403 +#: ../../apt-hook/json-hook.cc:489 #, c-format msgid "Learn more about Ubuntu Pro for 16.04 on Azure at %s" msgstr "Saiba mais sobre o Ubuntu Pro para a versão 16.04 no Azure em %s" -#: ../../apt-hook/json-hook.cc:412 +#: ../../apt-hook/json-hook.cc:498 #, c-format msgid "Learn more about Ubuntu Pro for 16.04 at %s" msgstr "Saiba mais sobre o Ubuntu Pro para a versão 16.04 em %s" -#: ../../apt-hook/json-hook.cc:423 +#: ../../apt-hook/json-hook.cc:509 #, c-format msgid "Learn more about Ubuntu Pro for 18.04 on Azure at %s" msgstr "Saiba mais sobre o Ubuntu Pro para a versão 18.04 no Azure em %s" -#: ../../apt-hook/json-hook.cc:432 +#: ../../apt-hook/json-hook.cc:518 #, c-format msgid "Learn more about Ubuntu Pro for 18.04 at %s" msgstr "Saiba mais sobre o Ubuntu Pro para a versão 18.04 em %s" -#: ../../apt-hook/json-hook.cc:443 +#: ../../apt-hook/json-hook.cc:529 #, c-format msgid "Learn more about Ubuntu Pro on Azure at %s" msgstr "Saiba mais sobre o Ubuntu Pro no Azure em %s" -#: ../../apt-hook/json-hook.cc:452 +#: ../../apt-hook/json-hook.cc:538 #, c-format msgid "Learn more about Ubuntu Pro on AWS at %s" msgstr "Saiba mais sobre o Ubuntu Pro na AWS em %s" -#: ../../apt-hook/json-hook.cc:461 +#: ../../apt-hook/json-hook.cc:547 #, c-format msgid "Learn more about Ubuntu Pro on GCP at %s" msgstr "Saiba mais sobre o Ubuntu Pro no GCP em %s" -#: ../../apt-hook/json-hook.cc:472 +#: ../../apt-hook/json-hook.cc:558 #, c-format msgid "Learn more about Ubuntu Pro at %s" msgstr "Saiba mais sobre o Ubuntu Pro em %s" -#: ../../apt-hook/json-hook.cc:485 +#: ../../apt-hook/json-hook.cc:584 #, c-format msgid "Get another security update through Ubuntu Pro with 'esm-apps' enabled:" msgid_plural "" @@ -245,7 +245,7 @@ "Obtenha mais atualizações de segurança através do Ubuntu Pro com 'esm-apps' " "habilitado:" -#: ../../apt-hook/json-hook.cc:494 +#: ../../apt-hook/json-hook.cc:593 #, c-format msgid "" "The following security update requires Ubuntu Pro with 'esm-infra' enabled:" @@ -258,6 +258,18 @@ "As seguintes atualizações de segurança requerem o Ubuntu Pro com 'esm-infra' " "habilitado:" +#: ../../apt-hook/json-hook.cc:609 +#, c-format +msgid "" +"The following packages will fail to download because your Ubuntu Pro " +"subscription has expired" +msgstr "" + +#: ../../apt-hook/json-hook.cc:618 +#, c-format +msgid "Renew your subscription or `sudo pro detach` to remove these errors" +msgstr "" + #. Description #: ../ubuntu-advantage-tools.templates:3 msgid "Ubuntu Pro support now requires ubuntu-advantage-pro" @@ -377,109 +389,113 @@ " sudo apt install ubuntu-pro-client\n" "para obter as últimas correções de erros e novas funcionalidades." +#: ../../uaclient/messages/__init__.py:112 +msgid "an unknown error" +msgstr "um erro desconhecido" + #. ############################################################################## #. GENERIC SYSTEM OPERATIONS # #. ############################################################################## -#: ../../uaclient/messages/__init__.py:118 +#: ../../uaclient/messages/__init__.py:119 #, python-brace-format msgid "Executing `{command}`" msgstr "Executando `{command}`" -#: ../../uaclient/messages/__init__.py:119 +#: ../../uaclient/messages/__init__.py:120 #, python-brace-format msgid "Executing `{command}` failed." msgstr "O comando `{command}` falhou" -#: ../../uaclient/messages/__init__.py:120 +#: ../../uaclient/messages/__init__.py:121 #, python-brace-format msgid "Invalid command specified '{cmd}'." msgstr "Comando inválido especificado: {cmd}" -#: ../../uaclient/messages/__init__.py:122 +#: ../../uaclient/messages/__init__.py:123 #, python-brace-format msgid "Failed running command '{cmd}' [exit({exit_code})]. Message: {stderr}" msgstr "" "Falha ao executar comando '{cmd}' [exit({exit_code})]. Mensagem: {stderr}" -#: ../../uaclient/messages/__init__.py:125 +#: ../../uaclient/messages/__init__.py:126 #, python-brace-format msgid "Installing {packages}" msgstr "Instalando {packages}" -#: ../../uaclient/messages/__init__.py:126 +#: ../../uaclient/messages/__init__.py:127 #, python-brace-format msgid "Installing {title} packages" msgstr "Instalando pacotes do {title}" -#: ../../uaclient/messages/__init__.py:127 +#: ../../uaclient/messages/__init__.py:128 msgid "Installing required snaps" msgstr "Instalando snaps necessários" -#: ../../uaclient/messages/__init__.py:129 +#: ../../uaclient/messages/__init__.py:130 #, python-brace-format msgid "Installing required snap: {snap}" msgstr "Instalando snap necessário: {snap}" -#: ../../uaclient/messages/__init__.py:132 +#: ../../uaclient/messages/__init__.py:133 #, python-brace-format msgid "Skipping installing packages: {packages}" msgstr "Não instalando os pacotes: {packages}" -#: ../../uaclient/messages/__init__.py:134 +#: ../../uaclient/messages/__init__.py:135 #, python-brace-format msgid "Uninstalling {packages}" msgstr "Desinstalando {packages}" -#: ../../uaclient/messages/__init__.py:136 +#: ../../uaclient/messages/__init__.py:137 #, python-brace-format msgid "Failure when uninstalling {packages}" msgstr "Falha ao desinstalar {packages}" -#: ../../uaclient/messages/__init__.py:139 +#: ../../uaclient/messages/__init__.py:140 #, python-brace-format msgid "Cannot install package {package} version {version}" msgstr "Não foi possível instalar o pacote {package} versão {version}" -#: ../../uaclient/messages/__init__.py:142 +#: ../../uaclient/messages/__init__.py:143 msgid "Failure checking APT policy." msgstr "Falha ao verificar 'APT policy'" -#: ../../uaclient/messages/__init__.py:143 +#: ../../uaclient/messages/__init__.py:144 msgid "Updating package lists" msgstr "Atualizando lista de pacotes" -#: ../../uaclient/messages/__init__.py:144 +#: ../../uaclient/messages/__init__.py:145 #, python-brace-format msgid "Updating {name} package lists" msgstr "Atualizando lista de pacotes: {name}" -#: ../../uaclient/messages/__init__.py:145 +#: ../../uaclient/messages/__init__.py:146 msgid "APT update failed." msgstr "APT update falhou" -#: ../../uaclient/messages/__init__.py:146 +#: ../../uaclient/messages/__init__.py:147 msgid "APT install failed." msgstr "APT install falhou" -#: ../../uaclient/messages/__init__.py:148 +#: ../../uaclient/messages/__init__.py:149 #, python-brace-format msgid "Backing up {original} as {backup}" msgstr "Salvando {original} como {backup}" -#: ../../uaclient/messages/__init__.py:149 +#: ../../uaclient/messages/__init__.py:150 msgid "The following package(s) will be REMOVED:" msgstr "" -#: ../../uaclient/messages/__init__.py:151 +#: ../../uaclient/messages/__init__.py:152 msgid "The following package(s) will be reinstalled from the archive:" msgstr "O(s) pacote(s) à seguir serão reinstalados:" -#: ../../uaclient/messages/__init__.py:162 +#: ../../uaclient/messages/__init__.py:163 #, python-brace-format msgid "" "*Your Ubuntu Pro subscription has EXPIRED*\n" -"{{pkg_num}} additional security update require Ubuntu Pro with '{{service}}' " -"enabled.\n" +"{{pkg_num}} additional security update requires Ubuntu Pro with " +"'{{service}}' enabled.\n" "Renew your subscription at {url}" msgid_plural "" "*Your Ubuntu Pro subscription has EXPIRED*\n" @@ -497,7 +513,7 @@ "'{{service}}' habilitado.\n" "Renove sua assinatura em {url}" -#: ../../uaclient/messages/__init__.py:175 +#: ../../uaclient/messages/__init__.py:176 #, python-brace-format msgid "" "CAUTION: Your Ubuntu Pro subscription will expire in {{remaining_days}} " @@ -520,7 +536,7 @@ "Renove sua assinatura em {url} para garantir a\n" "continuidade da cobertura de segurança para suas aplicações." -#: ../../uaclient/messages/__init__.py:188 +#: ../../uaclient/messages/__init__.py:189 #, python-brace-format msgid "" "CAUTION: Your Ubuntu Pro subscription expired on {{expired_date}}.\n" @@ -543,7 +559,7 @@ "continuidade da cobertura de segurança para suas aplicações. Seu período de " "carência vai expirar em {{remaining_days}} dias" -#: ../../uaclient/messages/__init__.py:202 +#: ../../uaclient/messages/__init__.py:203 #, python-brace-format msgid "" "*Your Ubuntu Pro subscription has EXPIRED*\n" @@ -555,12 +571,12 @@ #. ############################################################################## #. CONFIGURATION # #. ############################################################################## -#: ../../uaclient/messages/__init__.py:213 +#: ../../uaclient/messages/__init__.py:214 #, python-brace-format msgid "Setting {service} proxy" msgstr "Definindo proxy para {service}" -#: ../../uaclient/messages/__init__.py:215 +#: ../../uaclient/messages/__init__.py:216 #, python-brace-format msgid "" "No proxy set in config; however, proxy is configured for: {{services}}.\n" @@ -570,12 +586,12 @@ "{{services}}.\n" "Veja {url} para mais informações sobre configuração de proxy no pro.\n" -#: ../../uaclient/messages/__init__.py:220 +#: ../../uaclient/messages/__init__.py:221 #, python-brace-format msgid "Setting {scope} APT proxy" msgstr "Configurando APT proxy {scope}" -#: ../../uaclient/messages/__init__.py:222 +#: ../../uaclient/messages/__init__.py:223 msgid "" "\n" "Error: Setting global apt proxy and pro scoped apt proxy at the same time is " @@ -585,12 +601,12 @@ "Erro: A definição do proxy apt em escopo global e no escopo 'pro' ao mesmo " "tempo não é suportada. Nenhum proxy apt alterado." -#: ../../uaclient/messages/__init__.py:226 +#: ../../uaclient/messages/__init__.py:227 #, python-brace-format msgid "Warning: {old} has been renamed to {new}." msgstr "Atenção: {old} foi renomeado para {new}." -#: ../../uaclient/messages/__init__.py:230 +#: ../../uaclient/messages/__init__.py:231 #, python-brace-format msgid "" "Warning: Setting the {current_proxy} proxy will overwrite the " @@ -601,18 +617,18 @@ "{previous_proxy},\n" "previamente definido via `pro config`.\n" -#: ../../uaclient/messages/__init__.py:236 +#: ../../uaclient/messages/__init__.py:237 #, python-brace-format msgid "" "Using deprecated \"{old}\" config field.\n" "Please migrate to using \"{new}\"\n" msgstr "" -#: ../../uaclient/messages/__init__.py:243 +#: ../../uaclient/messages/__init__.py:244 msgid "Migrating /etc/ubuntu-advantage/uaclient.conf" msgstr "Migrando /etc/ubuntu-advantage/uaclient.conf" -#: ../../uaclient/messages/__init__.py:246 +#: ../../uaclient/messages/__init__.py:247 msgid "" "Warning: Failed to load /etc/ubuntu-advantage/uaclient.conf.preinst-backup\n" " No automatic migration will occur.\n" @@ -623,7 +639,7 @@ " Você talvez precise do comando \"pro config set\" para resetar suas " "configurações." -#: ../../uaclient/messages/__init__.py:253 +#: ../../uaclient/messages/__init__.py:254 msgid "" "Warning: Failed to migrate user_config from /etc/ubuntu-advantage/uaclient." "conf\n" @@ -634,7 +650,7 @@ " Por favor, execute os seguintes comandos para manter sua " "configuração atual:" -#: ../../uaclient/messages/__init__.py:259 +#: ../../uaclient/messages/__init__.py:260 msgid "" "Warning: Failed to migrate /etc/ubuntu-advantage/uaclient.conf\n" " Please add following to uaclient.conf to keep your config:" @@ -643,7 +659,7 @@ " Adicione o seguinte conteúdo a uaclient.conf para manter sua " "configuração:" -#: ../../uaclient/messages/__init__.py:272 +#: ../../uaclient/messages/__init__.py:273 msgid "" "Currently attempting to automatically attach this machine to an Ubuntu Pro " "subscription" @@ -651,35 +667,35 @@ "Tentando agora vincular automaticamente esta máquina a uma assinatura do " "Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:276 +#: ../../uaclient/messages/__init__.py:277 #, python-brace-format msgid "This machine is now attached to '{contract_name}'\n" msgstr "Esta máquina está agora vinculada a '{contract_name}'\n" -#: ../../uaclient/messages/__init__.py:281 +#: ../../uaclient/messages/__init__.py:282 msgid "This machine is now successfully attached'\n" msgstr "Esta máquina foi vinculada com sucesso\n" -#: ../../uaclient/messages/__init__.py:285 +#: ../../uaclient/messages/__init__.py:286 #, python-brace-format msgid "Enabling default service {name}" msgstr "Habilitando serviço {name}" -#: ../../uaclient/messages/__init__.py:287 +#: ../../uaclient/messages/__init__.py:288 #, python-brace-format msgid "Service {name} is recommended by default. Run: sudo pro enable {name}" msgstr "" "O serviço {name} é recomendado por padrão. Execute: sudo pro enable {name}" -#: ../../uaclient/messages/__init__.py:290 +#: ../../uaclient/messages/__init__.py:291 msgid "Initiating attach operation..." msgstr "Começando a operação de vínculo..." -#: ../../uaclient/messages/__init__.py:291 +#: ../../uaclient/messages/__init__.py:292 msgid "Failed to perform attach..." msgstr "Falha ao vincular" -#: ../../uaclient/messages/__init__.py:293 +#: ../../uaclient/messages/__init__.py:294 #, python-brace-format msgid "" "Please sign in to your Ubuntu Pro account at this link:\n" @@ -690,40 +706,45 @@ "{url}\n" "E forneça o código: {bold}{{user_code}}{end_bold}" -#: ../../uaclient/messages/__init__.py:302 +#: ../../uaclient/messages/__init__.py:303 msgid "Attaching the machine..." msgstr "Vinculando a máquina..." -#: ../../uaclient/messages/__init__.py:307 +#: ../../uaclient/messages/__init__.py:308 msgid "Detach will disable the following service:" msgid_plural "Detach will disable the following services:" msgstr[0] "Desvincular vai desabilitar o serviço:" msgstr[1] "Desvincular vai desabilitar os serviços:" -#: ../../uaclient/messages/__init__.py:312 +#: ../../uaclient/messages/__init__.py:313 msgid "This machine is now detached." msgstr "Esta máquina está desvinculada" -#: ../../uaclient/messages/__init__.py:316 +#: ../../uaclient/messages/__init__.py:317 msgid "One moment, checking your subscription first" msgstr "Um momento, checando a sua assinatura" -#: ../../uaclient/messages/__init__.py:318 +#: ../../uaclient/messages/__init__.py:319 +#, python-brace-format +msgid "Enabling {title}" +msgstr "Habilitando {title}" + +#: ../../uaclient/messages/__init__.py:320 #, python-brace-format msgid "{title} enabled" msgstr "{title} habilitado" -#: ../../uaclient/messages/__init__.py:319 +#: ../../uaclient/messages/__init__.py:321 #, python-brace-format msgid "{title} access enabled" msgstr "acesso habilitado para {title}" -#: ../../uaclient/messages/__init__.py:320 +#: ../../uaclient/messages/__init__.py:322 #, python-brace-format msgid "Could not enable {title}." msgstr "Não foi possível habilitar {title}" -#: ../../uaclient/messages/__init__.py:322 +#: ../../uaclient/messages/__init__.py:324 #, python-brace-format msgid "" "{service_being_enabled} cannot be enabled with {incompatible_service}.\n" @@ -735,12 +756,12 @@ "Desabilitar {incompatible_service} e prosseguir com a habilitação do " "{service_being_enabled}? (y/N) " -#: ../../uaclient/messages/__init__.py:328 +#: ../../uaclient/messages/__init__.py:330 #, python-brace-format msgid "Disabling incompatible service: {service}" msgstr "Desabilitando serviço incompatível: {service}" -#: ../../uaclient/messages/__init__.py:331 +#: ../../uaclient/messages/__init__.py:333 #, python-brace-format msgid "" "{service_being_enabled} cannot be enabled with {required_service} disabled.\n" @@ -752,23 +773,33 @@ "Habilitar {required_service} e prosseguir com a habilitação do " "{service_being_enabled}? (y/N) " -#: ../../uaclient/messages/__init__.py:336 +#: ../../uaclient/messages/__init__.py:338 #, python-brace-format msgid "Enabling required service: {service}" msgstr "Habilitando serviço necessário: {service}" -#: ../../uaclient/messages/__init__.py:338 +#: ../../uaclient/messages/__init__.py:340 #, python-brace-format msgid "A reboot is required to complete {operation}." msgstr "É necessário reiniciar a máquina para completar a operação {operation}" -#. DISABLE #: ../../uaclient/messages/__init__.py:343 #, python-brace-format +msgid "Configuring APT access to {service}" +msgstr "" + +#. DISABLE +#: ../../uaclient/messages/__init__.py:346 +#, python-brace-format +msgid "Removing APT access to {title}" +msgstr "" + +#: ../../uaclient/messages/__init__.py:347 +#, python-brace-format msgid "Could not disable {title}." msgstr "Não foi possível desabilitar {title}." -#: ../../uaclient/messages/__init__.py:345 +#: ../../uaclient/messages/__init__.py:349 #, python-brace-format msgid "" "{dependent_service} depends on {service_being_disabled}.\n" @@ -779,50 +810,55 @@ "Desabilitar {dependent_service} e prosseguir com a desabilitação do " "{service_being_disabled}? (y/N) " -#: ../../uaclient/messages/__init__.py:351 +#: ../../uaclient/messages/__init__.py:355 #, python-brace-format msgid "Disabling dependent service: {required_service}" msgstr "Desabilitando serviço dependente: {required_service}" -#: ../../uaclient/messages/__init__.py:354 +#: ../../uaclient/messages/__init__.py:358 #, python-brace-format msgid "Removing apt source file: {filename}" msgstr "Removendo arquivo do apt: {filename}" -#: ../../uaclient/messages/__init__.py:356 +#: ../../uaclient/messages/__init__.py:360 #, python-brace-format msgid "Removing apt preferences file: {filename}" msgstr "Removendo arquivo de preferência do apt: {filename}" -#: ../../uaclient/messages/__init__.py:361 +#: ../../uaclient/messages/__init__.py:363 +#, python-brace-format +msgid "Uninstalling all packages installed from {title}" +msgstr "Desinstalando todos os pacotes instalados por {title}" + +#: ../../uaclient/messages/__init__.py:368 msgid "(The --purge flag is still experimental - use with caution)" msgstr "" -#: ../../uaclient/messages/__init__.py:364 +#: ../../uaclient/messages/__init__.py:371 #, python-brace-format msgid "Purging the {service} packages would uninstall the following kernel(s):" msgstr "" -#: ../../uaclient/messages/__init__.py:367 +#: ../../uaclient/messages/__init__.py:374 #, python-brace-format msgid "{kernel_version} is the current running kernel." msgstr "" -#: ../../uaclient/messages/__init__.py:370 +#: ../../uaclient/messages/__init__.py:377 msgid "" "No other valid Ubuntu kernel was found in the system.\n" "Removing the package would potentially make the system unbootable.\n" "Aborting.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:377 +#: ../../uaclient/messages/__init__.py:384 msgid "" "If you cannot guarantee that other kernels in this system are bootable and\n" "working properly, *do not proceed*. You may end up with an unbootable " "system.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:386 +#: ../../uaclient/messages/__init__.py:393 #, python-brace-format msgid "" "Failed to automatically attach to an Ubuntu Pro subscription {num_attempts} " @@ -837,7 +873,7 @@ "A próxima tentativa está agendada para {next_run_datestring}.\n" "Você pode tentar manualmente executando `sudo pro auto-attach`." -#: ../../uaclient/messages/__init__.py:394 +#: ../../uaclient/messages/__init__.py:401 #, python-brace-format msgid "" "Failed to automatically attach to an Ubuntu Pro subscription {num_attempts} " @@ -854,7 +890,7 @@ "bug ubuntu-advantage-tools`\n" "Você pode tentar manualmente executando `sudo pro auto-attach`." -#: ../../uaclient/messages/__init__.py:402 +#: ../../uaclient/messages/__init__.py:409 #, python-brace-format msgid "" "Canonical servers did not recognize this machine as Ubuntu Pro: \"{detail}\"" @@ -862,41 +898,37 @@ "Os servidores da Canonical não reconheceram essa máquina como sendo " "associada ao Ubuntu Pro: \"{detail}\"" -#: ../../uaclient/messages/__init__.py:406 +#: ../../uaclient/messages/__init__.py:413 msgid "Canonical servers did not recognize this image as Ubuntu Pro" msgstr "" "Os servidores da Canonical não reconheceram essa imagem como sendo associada " "ao Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:408 +#: ../../uaclient/messages/__init__.py:415 #, python-brace-format msgid "the pro lock was held by pid {pid}" msgstr "A lock do pro está sendo mantida pelo pid {pid}" -#: ../../uaclient/messages/__init__.py:410 +#: ../../uaclient/messages/__init__.py:417 #, python-brace-format msgid "an error from Canonical servers: \"{error_msg}\"" msgstr "um erro dos servidores da Canonical: \"{error_msg}\"" -#: ../../uaclient/messages/__init__.py:412 +#: ../../uaclient/messages/__init__.py:419 msgid "a connectivity error" msgstr "um erro de conexão" -#: ../../uaclient/messages/__init__.py:413 +#: ../../uaclient/messages/__init__.py:420 #, python-brace-format msgid "an error while reaching {url}" msgstr "um error ao acessar {url}" -#: ../../uaclient/messages/__init__.py:414 -msgid "an unknown error" -msgstr "um erro desconhecido" - -#: ../../uaclient/messages/__init__.py:418 +#: ../../uaclient/messages/__init__.py:424 #, python-brace-format msgid "Due to contract refresh, '{service}' is now disabled." msgstr "Devido à atualização de contrato, '{service}' está agora desabilitado" -#: ../../uaclient/messages/__init__.py:421 +#: ../../uaclient/messages/__init__.py:427 #, python-brace-format msgid "" "Unable to disable '{service}' as recommended during contract refresh. " @@ -905,49 +937,49 @@ "Falha ao desabilitar '{service}' conforme esperado após atualização do seu " "contrato. O serviço ainda está ativo. Execute `pro status` para confirmar" -#: ../../uaclient/messages/__init__.py:426 +#: ../../uaclient/messages/__init__.py:432 #, python-brace-format msgid "Updating '{service}' on changed directives." msgstr "Atualizando '{service}' baseado nas novas diretivas" -#: ../../uaclient/messages/__init__.py:429 +#: ../../uaclient/messages/__init__.py:435 #, python-brace-format msgid "Updating '{service}' apt sources list on changed directives." msgstr "" "Atualizando a lista de apt sources do '{service}' baseado nas novas diretivas" -#: ../../uaclient/messages/__init__.py:432 +#: ../../uaclient/messages/__init__.py:438 #, python-brace-format msgid "Installing packages on changed directives: {packages}" msgstr "Instalando pacotes baseado nas novas diretivas: {packages}" -#: ../../uaclient/messages/__init__.py:442 +#: ../../uaclient/messages/__init__.py:448 #, python-brace-format msgid "Choose: [S]ubscribe at {url} [A]ttach existing token [C]ancel" msgstr "" "Escolha: [S] para vincular através de {url}; [A] para vincular usando um " "token existente; [C] para cancelar:" -#: ../../uaclient/messages/__init__.py:446 +#: ../../uaclient/messages/__init__.py:452 #, python-brace-format msgid "Choose: [E]nable {service} [C]ancel" msgstr "Escolha: [E] para habilitar {service}; [C] para cancelar:" -#: ../../uaclient/messages/__init__.py:450 +#: ../../uaclient/messages/__init__.py:456 #, python-brace-format msgid "Choose: [R]enew your subscription (at {url}) [C]ancel" msgstr "Escolha: [R]enovar sua assinatura (em {url}); [C]ancelar" -#: ../../uaclient/messages/__init__.py:453 +#: ../../uaclient/messages/__init__.py:459 #, python-brace-format msgid "A fix is available in {fix_stream}." msgstr "Uma solução está disponível em {fix_stream}." -#: ../../uaclient/messages/__init__.py:454 +#: ../../uaclient/messages/__init__.py:460 msgid "The update is not yet installed." msgstr "A atualização ainda não está instalada" -#: ../../uaclient/messages/__init__.py:456 +#: ../../uaclient/messages/__init__.py:462 msgid "" "The update is not installed because this system is not attached to a\n" "subscription.\n" @@ -955,7 +987,7 @@ "A atualização não pode ser instalada porque o sistema não está vinculado a\n" "uma assinatura.\n" -#: ../../uaclient/messages/__init__.py:462 +#: ../../uaclient/messages/__init__.py:468 msgid "" "The update is not installed because this system is attached to an\n" "expired subscription.\n" @@ -963,7 +995,7 @@ "A atualização não pode ser instalada porque o sistema está vinculado a uma\n" "assinatura vencida.\n" -#: ../../uaclient/messages/__init__.py:468 +#: ../../uaclient/messages/__init__.py:474 #, python-brace-format msgid "" "The update is not installed because this system does not have\n" @@ -973,11 +1005,11 @@ "{service}\n" "habilitado.\n" -#: ../../uaclient/messages/__init__.py:473 +#: ../../uaclient/messages/__init__.py:479 msgid "The update is already installed." msgstr "A atualização já está instalada." -#: ../../uaclient/messages/__init__.py:475 +#: ../../uaclient/messages/__init__.py:481 #, python-brace-format msgid "" "For easiest security on {title}, use Ubuntu Pro instances.\n" @@ -987,73 +1019,73 @@ "Ubuntu Pro.\n" "Aprenda mais em {cloud_specific_url}" -#: ../../uaclient/messages/__init__.py:480 +#: ../../uaclient/messages/__init__.py:486 msgid "requested" msgstr "requisitado" -#: ../../uaclient/messages/__init__.py:481 +#: ../../uaclient/messages/__init__.py:487 msgid "related" msgstr "relacionado" -#: ../../uaclient/messages/__init__.py:482 +#: ../../uaclient/messages/__init__.py:488 #, python-brace-format msgid " {issue} is resolved." msgstr " {issue} resolvida" -#: ../../uaclient/messages/__init__.py:484 +#: ../../uaclient/messages/__init__.py:490 #, python-brace-format msgid " {issue} [{context}] is resolved." msgstr "{issue} [{context}] foi resolvida" -#: ../../uaclient/messages/__init__.py:486 +#: ../../uaclient/messages/__init__.py:492 #, python-brace-format msgid " {issue} is not resolved." msgstr " {issue} não foi resolvida" -#: ../../uaclient/messages/__init__.py:488 +#: ../../uaclient/messages/__init__.py:494 #, python-brace-format msgid " {issue} [{context}] is not resolved." msgstr " {issue} [{context}] não foi resolvida" -#: ../../uaclient/messages/__init__.py:491 +#: ../../uaclient/messages/__init__.py:497 #, python-brace-format msgid " {issue} does not affect your system." msgstr " {issue} não afeta o seu sistema." -#: ../../uaclient/messages/__init__.py:494 +#: ../../uaclient/messages/__init__.py:500 #, python-brace-format msgid " {issue} [{context}] does not affect your system." msgstr " {issue} [{context}] não afeta o seu sistema." -#: ../../uaclient/messages/__init__.py:498 +#: ../../uaclient/messages/__init__.py:504 #, python-brace-format msgid "{num_pkgs} package is still affected: {pkgs}" msgid_plural "{num_pkgs} packages are still affected: {pkgs}" msgstr[0] "{num_pkgs} pacote ainda está afetado: {pkgs}" msgstr[1] "{num_pkgs} pacotes ainda estão afetados: {pkgs}" -#: ../../uaclient/messages/__init__.py:505 +#: ../../uaclient/messages/__init__.py:511 #, python-brace-format msgid "{count} affected source package is installed: {pkgs}" msgid_plural "{count} affected source packages are installed: {pkgs}" msgstr[0] "{count} pacote fonte afetado está instalado: {pkgs}" msgstr[1] "{count} pacotes fonte afetados estão instalados: {pkgs}" -#: ../../uaclient/messages/__init__.py:511 +#: ../../uaclient/messages/__init__.py:517 msgid "No affected source packages are installed." msgstr "Nenhum pacote fonte afetado está instalado" -#: ../../uaclient/messages/__init__.py:513 +#: ../../uaclient/messages/__init__.py:519 #, python-brace-format msgid "{issue} is resolved." msgstr "{issue} foi resolvida" -#: ../../uaclient/messages/__init__.py:515 +#: ../../uaclient/messages/__init__.py:521 #, python-brace-format msgid " {issue} is resolved by livepatch patch version: {version}." msgstr " {issue} for resolvida pelo patch {version} do livepatch." -#: ../../uaclient/messages/__init__.py:518 +#: ../../uaclient/messages/__init__.py:524 #, python-brace-format msgid "" "{bold}Ubuntu Pro service: {{service}} is not enabled.\n" @@ -1068,7 +1100,7 @@ "este serviço.\n" "{{{{ pro enable {{service}} }}}}{end_bold}" -#: ../../uaclient/messages/__init__.py:525 +#: ../../uaclient/messages/__init__.py:531 #, python-brace-format msgid "" "{bold}The machine is not attached to an Ubuntu Pro subscription.\n" @@ -1080,7 +1112,7 @@ "para o Ubuntu Pro.\n" "{{ pro attach TOKEN }}{end_bold}" -#: ../../uaclient/messages/__init__.py:531 +#: ../../uaclient/messages/__init__.py:537 #, python-brace-format msgid "" "{bold}The machine has an expired subscription.\n" @@ -1095,7 +1127,7 @@ "{{ pro detach --assume-yes }}\n" "{{ pro attach NEW_TOKEN }}{end_bold}" -#: ../../uaclient/messages/__init__.py:539 +#: ../../uaclient/messages/__init__.py:545 #, python-brace-format msgid "" "{bold}WARNING: The option --dry-run is being used.\n" @@ -1104,7 +1136,7 @@ "{bold}ATENÇÂO: a opção --dry-run está sendo usada.\n" "Nenhum pacote será instalado na execução deste comando.{end_bold}" -#: ../../uaclient/messages/__init__.py:544 +#: ../../uaclient/messages/__init__.py:550 #, python-brace-format msgid "" "Error: Ubuntu Pro service: {service} is not enabled.\n" @@ -1113,7 +1145,7 @@ "Erro: o serviço Ubuntu Pro: {service} não está habilitado.\n" "Sem o serviço, não podemos corrigir o sistema." -#: ../../uaclient/messages/__init__.py:549 +#: ../../uaclient/messages/__init__.py:555 #, python-brace-format msgid "" "Error: The current Ubuntu Pro subscription is not entitled to: {service}.\n" @@ -1123,74 +1155,74 @@ "Ubuntu Pro.\n" "Sem o serviço, não podemos corrigir o sistema." -#: ../../uaclient/messages/__init__.py:554 +#: ../../uaclient/messages/__init__.py:560 #, python-brace-format msgid "{service} is required for upgrade." msgstr "{service} é necessário para atualização." -#: ../../uaclient/messages/__init__.py:558 +#: ../../uaclient/messages/__init__.py:564 #, python-brace-format msgid "{service} is required for upgrade, but current subscription is expired." msgstr "" "{service} é necessário para atualização, mas a atual assinatura está vencida" -#: ../../uaclient/messages/__init__.py:562 +#: ../../uaclient/messages/__init__.py:568 #, python-brace-format msgid "{service} is required for upgrade, but it is not enabled." msgstr "" "{service} é necessário para atualização, mas o serviço está desabilitado." -#: ../../uaclient/messages/__init__.py:566 +#: ../../uaclient/messages/__init__.py:572 msgid "APT failed to install the package.\n" msgstr "APT falhou ao instalar o pacote.\n" -#: ../../uaclient/messages/__init__.py:571 +#: ../../uaclient/messages/__init__.py:577 msgid "Sorry, no fix is available yet." msgstr "Desculpe, não existe uma correção disponível ainda." -#: ../../uaclient/messages/__init__.py:575 +#: ../../uaclient/messages/__init__.py:581 msgid "Ubuntu security engineers are investigating this issue." msgstr "Engenheiros de segurança do Ubuntu estão investigando o problema." -#: ../../uaclient/messages/__init__.py:579 +#: ../../uaclient/messages/__init__.py:585 msgid "A fix is coming soon. Try again tomorrow." msgstr "Uma correção será entregue em breve. Tente de novo amanhã." -#: ../../uaclient/messages/__init__.py:583 +#: ../../uaclient/messages/__init__.py:589 msgid "Sorry, no fix is available." msgstr "Desculpe, não existe uma correção disponível." -#: ../../uaclient/messages/__init__.py:587 +#: ../../uaclient/messages/__init__.py:593 msgid "Source package does not exist on this release." msgstr "O pacote fonte não existe para esta versão do Ubuntu." -#: ../../uaclient/messages/__init__.py:591 +#: ../../uaclient/messages/__init__.py:597 msgid "Source package is not affected on this release." msgstr "O pacote fonte não é afetado nesta versão do Ubuntu." -#: ../../uaclient/messages/__init__.py:595 +#: ../../uaclient/messages/__init__.py:601 #, python-brace-format msgid "UNKNOWN: {status}" msgstr "DESCONHECIDO: {status}" -#: ../../uaclient/messages/__init__.py:599 +#: ../../uaclient/messages/__init__.py:605 msgid "Associated CVEs:" msgstr "CVEs associadas:" -#: ../../uaclient/messages/__init__.py:600 +#: ../../uaclient/messages/__init__.py:606 msgid "Found Launchpad bugs:" msgstr "Bugs do Launchpad encontrados:" -#: ../../uaclient/messages/__init__.py:602 +#: ../../uaclient/messages/__init__.py:608 #, python-brace-format msgid "Fixing requested {issue_id}" msgstr "Corrigindo {issue_id}" -#: ../../uaclient/messages/__init__.py:606 +#: ../../uaclient/messages/__init__.py:612 msgid "Fixing related USNs:" msgstr "Corrigindo USNs relacionados" -#: ../../uaclient/messages/__init__.py:610 +#: ../../uaclient/messages/__init__.py:616 #, python-brace-format msgid "" "Found related USNs:\n" @@ -1199,11 +1231,11 @@ "USNs relacionadas foram encontradas:\n" "- {related_usns}" -#: ../../uaclient/messages/__init__.py:614 +#: ../../uaclient/messages/__init__.py:620 msgid "Summary:" msgstr "Sumário:" -#: ../../uaclient/messages/__init__.py:618 +#: ../../uaclient/messages/__init__.py:624 #, python-brace-format msgid "" "Even though a related USN failed to be fixed, note\n" @@ -1220,21 +1252,21 @@ "\n" "{url}\n" -#: ../../uaclient/messages/__init__.py:627 +#: ../../uaclient/messages/__init__.py:633 msgid "Ubuntu standard updates" msgstr "Atualizações padrão do Ubuntu" -#: ../../uaclient/messages/__init__.py:628 -#: ../../uaclient/messages/__init__.py:1222 +#: ../../uaclient/messages/__init__.py:634 +#: ../../uaclient/messages/__init__.py:1242 msgid "Ubuntu Pro: ESM Infra" msgstr "" -#: ../../uaclient/messages/__init__.py:629 -#: ../../uaclient/messages/__init__.py:1208 +#: ../../uaclient/messages/__init__.py:635 +#: ../../uaclient/messages/__init__.py:1228 msgid "Ubuntu Pro: ESM Apps" msgstr "" -#: ../../uaclient/messages/__init__.py:632 +#: ../../uaclient/messages/__init__.py:638 msgid "" "Package fixes cannot be installed.\n" "To install them, run this command as root (try using sudo)" @@ -1242,12 +1274,12 @@ "As atualizações de pacotes não podem ser instaladas.\n" "Para instalar os pacotes, execute esse comando como root (tente usando sudo)" -#: ../../uaclient/messages/__init__.py:638 +#: ../../uaclient/messages/__init__.py:644 #, python-brace-format msgid "Enter your token (from {url}) to attach this system:" msgstr "Insira seu token (da {url}) para vincular seu sistema:" -#: ../../uaclient/messages/__init__.py:642 +#: ../../uaclient/messages/__init__.py:648 msgid "Enter your new token to renew Ubuntu Pro subscription on this system:" msgstr "" "Providencie seu novo token para renovar sua assinatura do Ubuntu Pro neste " @@ -1256,33 +1288,33 @@ #. ############################################################################## #. SECURITYSTATUS SUBCOMMAND # #. ############################################################################## -#: ../../uaclient/messages/__init__.py:652 +#: ../../uaclient/messages/__init__.py:658 #, python-brace-format msgid "{count} packages installed:" msgstr "{count} pacotes instalados:" -#: ../../uaclient/messages/__init__.py:655 +#: ../../uaclient/messages/__init__.py:661 #, python-brace-format msgid "{offset}{count} package from Ubuntu {repository} repository" msgid_plural "{offset}{count} packages from Ubuntu {repository} repository" msgstr[0] "{offset}{count} pacote do repositório {repository} do Ubuntu" msgstr[1] "{offset}{count} pacotes do repositório {repository} do Ubuntu" -#: ../../uaclient/messages/__init__.py:662 +#: ../../uaclient/messages/__init__.py:668 #, python-brace-format msgid "{offset}{count} package from a third party" msgid_plural "{offset}{count} packages from third parties" msgstr[0] "{offset}{count} pacote de terceiros" msgstr[1] "{offset}{count} pacotes de terceiros" -#: ../../uaclient/messages/__init__.py:669 +#: ../../uaclient/messages/__init__.py:675 #, python-brace-format msgid "{offset}{count} package no longer available for download" msgid_plural "{offset}{count} packages no longer available for download" msgstr[0] "{offset}{count} pacote não mais disponível para download" msgstr[1] "{offset}{count} pacotes não mais disponíveis para download" -#: ../../uaclient/messages/__init__.py:676 +#: ../../uaclient/messages/__init__.py:682 msgid "" "To get more information about the packages, run\n" " pro security-status --help\n" @@ -1292,7 +1324,7 @@ " pro security-status --help\n" "para uma lista das opções disponíveis." -#: ../../uaclient/messages/__init__.py:683 +#: ../../uaclient/messages/__init__.py:689 msgid "" " Make sure to run\n" " sudo apt update\n" @@ -1302,21 +1334,21 @@ " sudo apt update\n" "para obter as informações mais recentes dos pacotes direto do apt." -#: ../../uaclient/messages/__init__.py:689 +#: ../../uaclient/messages/__init__.py:695 #, python-brace-format msgid "The system apt information was updated {days} day(s) ago." msgstr "As informações do apt foram atualizadas há {days} dia(s) atrás" -#: ../../uaclient/messages/__init__.py:693 +#: ../../uaclient/messages/__init__.py:699 msgid "The system apt cache may be outdated." msgstr "O cache do apt pode estar desatualizado" -#: ../../uaclient/messages/__init__.py:697 +#: ../../uaclient/messages/__init__.py:703 #, python-brace-format msgid "Main/Restricted packages receive updates until {date}." msgstr "pacotes Main/Restricted receberão atualizações até {date}." -#: ../../uaclient/messages/__init__.py:700 +#: ../../uaclient/messages/__init__.py:706 #, python-brace-format msgid "" "This machine is receiving security patching for Ubuntu Main/Restricted\n" @@ -1325,15 +1357,15 @@ "Esta máquina está recebendo patches de segurança para o repositório\n" "Main/Restricted do Ubuntu até {date}" -#: ../../uaclient/messages/__init__.py:706 +#: ../../uaclient/messages/__init__.py:712 msgid "This machine is attached to an Ubuntu Pro subscription." msgstr "Esta máquina está vinculada a uma assinatura do Ubuntu Pro." -#: ../../uaclient/messages/__init__.py:709 +#: ../../uaclient/messages/__init__.py:715 msgid "This machine is NOT attached to an Ubuntu Pro subscription." msgstr "Esta máquina NÃO está vinculada a uma assinatura do Ubuntu Pro." -#: ../../uaclient/messages/__init__.py:713 +#: ../../uaclient/messages/__init__.py:719 msgid "" "Packages from third parties are not provided by the official Ubuntu\n" "archive, for example packages from Personal Package Archives in Launchpad." @@ -1342,7 +1374,7 @@ "por examplo, pacotes de encontrados em Personal Package Archives (PPAs) do " "Launchpad." -#: ../../uaclient/messages/__init__.py:718 +#: ../../uaclient/messages/__init__.py:724 msgid "" "Packages that are not available for download may be left over from a\n" "previous release of Ubuntu, may have been installed directly from a\n" @@ -1352,7 +1384,7 @@ "versões anteriores do Ubuntu, podem ter sido instalados diretamente por um\n" "arquivo .deb, ou de uma fonte que foi desabilitada." -#: ../../uaclient/messages/__init__.py:725 +#: ../../uaclient/messages/__init__.py:731 msgid "" "This machine is NOT receiving security patches because the LTS period has " "ended\n" @@ -1362,7 +1394,7 @@ "acabou\n" "e esm-infra não está habilitado." -#: ../../uaclient/messages/__init__.py:731 +#: ../../uaclient/messages/__init__.py:737 #, python-brace-format msgid "" "Ubuntu Pro with '{service}' enabled provides security updates for\n" @@ -1371,14 +1403,14 @@ "Ubuntu Pro com '{service}' habilitado provê atualizações de segurança\n" "para pacotes do {repository} até {year}." -#: ../../uaclient/messages/__init__.py:737 +#: ../../uaclient/messages/__init__.py:743 #, python-brace-format msgid "There is {updates} pending security update." msgid_plural "There are {updates} pending security updates." msgstr[0] "Existe {updates} atualização de segurança pendente." msgstr[1] "Existem {updates} atualizações de segurança pendentes." -#: ../../uaclient/messages/__init__.py:744 +#: ../../uaclient/messages/__init__.py:750 #, python-brace-format msgid "" "{repository} packages are receiving security updates from\n" @@ -1387,7 +1419,7 @@ "Pacotes do {repository} estão recebendo atualizações de segurança do\n" "Ubuntu Pro com '{service}' habilitado até {year}." -#: ../../uaclient/messages/__init__.py:750 +#: ../../uaclient/messages/__init__.py:756 #, python-brace-format msgid "" "You have received {updates} security\n" @@ -1402,12 +1434,12 @@ "Você recebeu {updates} atualizações de\n" "segurança." -#: ../../uaclient/messages/__init__.py:760 +#: ../../uaclient/messages/__init__.py:766 #, python-brace-format msgid "Enable {service} with: pro enable {service}" msgstr "Habilite {service} com: pro enable {service}" -#: ../../uaclient/messages/__init__.py:762 +#: ../../uaclient/messages/__init__.py:768 #, python-brace-format msgid "" "Try Ubuntu Pro with a free personal subscription on up to 5 machines.\n" @@ -1417,7 +1449,7 @@ "máquinas.\n" "Saiba mais em {url}\n" -#: ../../uaclient/messages/__init__.py:769 +#: ../../uaclient/messages/__init__.py:775 #, python-brace-format msgid "" "For example, run:\n" @@ -1428,165 +1460,165 @@ " apt-cache show {package}\n" "para saber mais sobre este pacote." -#: ../../uaclient/messages/__init__.py:776 +#: ../../uaclient/messages/__init__.py:782 msgid "You have no packages installed from a third party." msgstr "Você não tem pacotes instalados de terceitos." -#: ../../uaclient/messages/__init__.py:779 +#: ../../uaclient/messages/__init__.py:785 msgid "You have no packages installed that are no longer available." msgstr "Você não tem pacotes instalados que não estejam mais disponíveis." -#: ../../uaclient/messages/__init__.py:782 +#: ../../uaclient/messages/__init__.py:788 msgid "Ubuntu Pro is not available for non-LTS releases." msgstr "Ubuntu Pro não está disponível para versões do Ubuntu não LTS." -#: ../../uaclient/messages/__init__.py:785 +#: ../../uaclient/messages/__init__.py:791 #, python-brace-format msgid "Run 'pro help {service}' to learn more" msgstr "Execute 'pro help {service}' para saber mais" -#: ../../uaclient/messages/__init__.py:788 +#: ../../uaclient/messages/__init__.py:794 #, python-brace-format msgid "Installed packages with an available {service} update:" msgstr "Pacotes instalados com atualizações disponíveis por {service}:" -#: ../../uaclient/messages/__init__.py:791 +#: ../../uaclient/messages/__init__.py:797 #, python-brace-format msgid "Installed packages with an {service} update applied:" msgstr "Pacotes instalados com atualizações aplicadas por {service}:" -#: ../../uaclient/messages/__init__.py:793 +#: ../../uaclient/messages/__init__.py:799 #, python-brace-format msgid "Installed packages covered by {service}:" msgstr "Pacotes instalados cobertos por {service}:" -#: ../../uaclient/messages/__init__.py:795 +#: ../../uaclient/messages/__init__.py:801 #, python-brace-format msgid "Further installed packages covered by {service}:" msgstr "Pacotes adicionais instalados cobertos por {service}:" -#: ../../uaclient/messages/__init__.py:797 +#: ../../uaclient/messages/__init__.py:803 msgid "Packages:" msgstr "Pacotes:" #. ############################################################################## #. STATUS SUBCOMMAND # #. ############################################################################## -#: ../../uaclient/messages/__init__.py:805 +#: ../../uaclient/messages/__init__.py:811 msgid "SERVICE" msgstr "SERVIÇO" -#: ../../uaclient/messages/__init__.py:806 +#: ../../uaclient/messages/__init__.py:812 msgid "AVAILABLE" msgstr "DISPONÍVEL" -#: ../../uaclient/messages/__init__.py:807 +#: ../../uaclient/messages/__init__.py:813 msgid "ENTITLED" msgstr "INCLUÍDO" -#: ../../uaclient/messages/__init__.py:808 +#: ../../uaclient/messages/__init__.py:814 msgid "AUTO_ENABLED" msgstr "AUTO_HABILITADO" -#: ../../uaclient/messages/__init__.py:809 +#: ../../uaclient/messages/__init__.py:815 msgid "STATUS" msgstr "STATUS" -#: ../../uaclient/messages/__init__.py:810 +#: ../../uaclient/messages/__init__.py:816 msgid "DESCRIPTION" msgstr "DESCRIÇÃO" -#: ../../uaclient/messages/__init__.py:811 +#: ../../uaclient/messages/__init__.py:817 msgid "NOTICES" msgstr "NOTÍCIAS" -#: ../../uaclient/messages/__init__.py:812 +#: ../../uaclient/messages/__init__.py:818 msgid "FEATURES" msgstr "FUNCIONALIDADES" -#: ../../uaclient/messages/__init__.py:816 +#: ../../uaclient/messages/__init__.py:822 msgid "enabled" msgstr "habilitado" -#: ../../uaclient/messages/__init__.py:817 +#: ../../uaclient/messages/__init__.py:823 msgid "disabled" msgstr "desabilitado" -#: ../../uaclient/messages/__init__.py:818 +#: ../../uaclient/messages/__init__.py:824 msgid "n/a" msgstr "n/d" -#: ../../uaclient/messages/__init__.py:820 +#: ../../uaclient/messages/__init__.py:826 msgid "warning" msgstr "atenção" -#: ../../uaclient/messages/__init__.py:821 +#: ../../uaclient/messages/__init__.py:827 msgid "essential" msgstr "essencial" -#: ../../uaclient/messages/__init__.py:822 +#: ../../uaclient/messages/__init__.py:828 msgid "standard" msgstr "padrão" -#: ../../uaclient/messages/__init__.py:823 +#: ../../uaclient/messages/__init__.py:829 msgid "advanced" msgstr "avançado" -#: ../../uaclient/messages/__init__.py:825 +#: ../../uaclient/messages/__init__.py:831 msgid "Unknown/Expired" msgstr "Desconhecido/expirado" -#: ../../uaclient/messages/__init__.py:828 +#: ../../uaclient/messages/__init__.py:834 #, python-brace-format msgid "Enable services with: {command}" msgstr "Habilite serviços com: {command}" -#: ../../uaclient/messages/__init__.py:830 +#: ../../uaclient/messages/__init__.py:836 msgid "Account" msgstr "Conta" -#: ../../uaclient/messages/__init__.py:831 +#: ../../uaclient/messages/__init__.py:837 msgid "Subscription" msgstr "Assinatura" -#: ../../uaclient/messages/__init__.py:832 +#: ../../uaclient/messages/__init__.py:838 msgid "Valid until" msgstr "Válido até" -#: ../../uaclient/messages/__init__.py:833 +#: ../../uaclient/messages/__init__.py:839 msgid "Technical support level" msgstr "Nível de suporte técnico" -#: ../../uaclient/messages/__init__.py:835 +#: ../../uaclient/messages/__init__.py:841 msgid "This token is not valid." msgstr "Este token não é válido." -#: ../../uaclient/messages/__init__.py:836 +#: ../../uaclient/messages/__init__.py:842 msgid "No Ubuntu Pro operations are running" msgstr "Nenhuma operação Ubuntu Pro está sendo executada" -#: ../../uaclient/messages/__init__.py:839 +#: ../../uaclient/messages/__init__.py:845 msgid "No Ubuntu Pro services are available to this system." msgstr "Nenhum serviço do Ubuntu Pro está disponível neste sistema." -#: ../../uaclient/messages/__init__.py:842 +#: ../../uaclient/messages/__init__.py:848 msgid "For a list of all Ubuntu Pro services, run 'pro status --all'" msgstr "" "Para uma lista com todos os serviços do Ubuntu Pro, execute 'pro status --" "all'" -#: ../../uaclient/messages/__init__.py:844 +#: ../../uaclient/messages/__init__.py:850 msgid " * Service has variants" msgstr "* Serviço tem variantes" -#: ../../uaclient/messages/__init__.py:846 +#: ../../uaclient/messages/__init__.py:852 msgid "" "For a list of all Ubuntu Pro services and variants, run 'pro status --all'" msgstr "" "Para uma lista como todos os serviços e variantes do Ubuntu Pro, execute " "'pro status --all'" -#: ../../uaclient/messages/__init__.py:851 +#: ../../uaclient/messages/__init__.py:857 msgid "" "A change has been detected in your contract.\n" "Please run `sudo pro refresh`." @@ -1599,87 +1631,93 @@ #. ############################################################################## #. This encompasses help text for subcommands, flags, and arguments for the CLI #. Also, any generic strings about the CLI itself go here. -#: ../../uaclient/messages/__init__.py:864 +#: ../../uaclient/messages/__init__.py:870 msgid "Try 'pro --help' for more information." msgstr "Tente 'pro --help' para mais informações." -#: ../../uaclient/messages/__init__.py:866 +#: ../../uaclient/messages/__init__.py:872 #, python-brace-format msgid "Use {name} {command} --help for more information about a command." msgstr "Use {name} {command} --help para mais informações sobre um comando." -#: ../../uaclient/messages/__init__.py:869 +#: ../../uaclient/messages/__init__.py:875 msgid "Use pro help to get more details about each service" msgstr "Use pro help para obter mais detalhes sobre cada serviço" -#: ../../uaclient/messages/__init__.py:872 +#: ../../uaclient/messages/__init__.py:878 msgid "Variants:" msgstr "Variantes:" -#: ../../uaclient/messages/__init__.py:873 +#: ../../uaclient/messages/__init__.py:879 msgid "Arguments" msgstr "Argumentos" -#: ../../uaclient/messages/__init__.py:874 +#: ../../uaclient/messages/__init__.py:880 msgid "Flags" msgstr "" -#: ../../uaclient/messages/__init__.py:875 +#: ../../uaclient/messages/__init__.py:881 msgid "Available Commands" msgstr "Comandos Disponíveis" -#: ../../uaclient/messages/__init__.py:877 +#: ../../uaclient/messages/__init__.py:883 #, python-brace-format msgid "output in the specified format (default: {default})" msgstr "saída no formato especificado (default: {default})" -#: ../../uaclient/messages/__init__.py:880 +#: ../../uaclient/messages/__init__.py:886 #, python-brace-format msgid "do not prompt for confirmation before performing the {command}" msgstr "não peça por confirmação antes de executar {command}" -#: ../../uaclient/messages/__init__.py:883 -#: ../../uaclient/messages/__init__.py:1103 +#: ../../uaclient/messages/__init__.py:889 +#: ../../uaclient/messages/__init__.py:1123 msgid "Calls the Client API endpoints." msgstr "Chama os endpoints da API do Cliente." -#: ../../uaclient/messages/__init__.py:884 +#: ../../uaclient/messages/__init__.py:890 msgid "API endpoint to call" msgstr "API endpoints que podem ser executados" -#: ../../uaclient/messages/__init__.py:886 +#: ../../uaclient/messages/__init__.py:892 +msgid "" +"For endpoints that support progress updates, show each progress update on a " +"new line in JSON format" +msgstr "" + +#: ../../uaclient/messages/__init__.py:896 msgid "Options to pass to the API endpoint, formatted as key=value" msgstr "Opções para passar ao endpoint da API, formatadas como chave=valor" -#: ../../uaclient/messages/__init__.py:888 +#: ../../uaclient/messages/__init__.py:898 msgid "arguments in JSON format to the API endpoint" msgstr "argumentos em formato JSON para o endpoint da API" -#: ../../uaclient/messages/__init__.py:891 +#: ../../uaclient/messages/__init__.py:901 msgid "Automatically attach on an Ubuntu Pro cloud instance." msgstr "Automaticamente vincular em uma instância cloud do Ubuntu Pro." -#: ../../uaclient/messages/__init__.py:895 +#: ../../uaclient/messages/__init__.py:905 msgid "Collect logs and relevant system information into a tarball." msgstr "Coleta logs e outras informações relevantes do sistema em um tarball." -#: ../../uaclient/messages/__init__.py:898 -msgid "tarball where the logs will be stored. (Defaults to ./ua_logs.tar.gz)" -msgstr "tarball onde os logs serão guardados. (Valor padrão ./ua_logs.tar.gz)" +#: ../../uaclient/messages/__init__.py:908 +msgid "tarball where the logs will be stored. (Defaults to ./pro_logs.tar.gz)" +msgstr "tarball onde os logs serão guardados. (Valor padrão ./pro_logs.tar.gz)" -#: ../../uaclient/messages/__init__.py:901 +#: ../../uaclient/messages/__init__.py:911 msgid "Show customizable configuration settings" msgstr "Mostra opções personalizáveis da configuração" -#: ../../uaclient/messages/__init__.py:903 +#: ../../uaclient/messages/__init__.py:913 msgid "Optional key or key(s) to show configuration settings." msgstr "Valor ou valores opcionais ao mostrar as configurações." -#: ../../uaclient/messages/__init__.py:906 +#: ../../uaclient/messages/__init__.py:916 msgid "Set and apply Ubuntu Pro configuration settings" msgstr "Define e aplica as configurações do Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:909 +#: ../../uaclient/messages/__init__.py:919 #, python-brace-format msgid "" "key=value pair to configure for Ubuntu Pro services. Key must be one of: " @@ -1688,20 +1726,20 @@ "par chave=valor para configurar serviços Ubuntu Pro. Chave precisa ser uma " "dentre: {options}" -#: ../../uaclient/messages/__init__.py:912 +#: ../../uaclient/messages/__init__.py:922 msgid "Unset Ubuntu Pro configuration setting" msgstr "Desabilitar a configuração do Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:914 +#: ../../uaclient/messages/__init__.py:924 #, python-brace-format msgid "configuration key to unset from Ubuntu Pro services. One of: {options}" msgstr "chave de configuração Ubuntu Pro para desabilitar. Uma de: {options}" -#: ../../uaclient/messages/__init__.py:916 +#: ../../uaclient/messages/__init__.py:926 msgid "Manage Ubuntu Pro configuration" msgstr "Gerencie a configuração do Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:919 +#: ../../uaclient/messages/__init__.py:929 #, python-brace-format msgid "" "Attach this machine to an Ubuntu Pro subscription with a token obtained " @@ -1720,28 +1758,28 @@ "Ubuntu Pro por meio\n" "de um navegador." -#: ../../uaclient/messages/__init__.py:927 +#: ../../uaclient/messages/__init__.py:937 msgid "token obtained for Ubuntu Pro authentication" msgstr "token obtido para autenticação do Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:929 +#: ../../uaclient/messages/__init__.py:939 msgid "do not enable any recommended services automatically" msgstr "não habilite nenhum serviço recomendado automaticamente" -#: ../../uaclient/messages/__init__.py:932 +#: ../../uaclient/messages/__init__.py:942 msgid "" "use the provided attach config file instead of passing the token on the cli" msgstr "" "use para providenciar um arquivo de configuração ao vincular ao invés de " "inserir o token via linha de comando" -#: ../../uaclient/messages/__init__.py:937 +#: ../../uaclient/messages/__init__.py:947 msgid "" "Inspect and resolve CVEs and USNs (Ubuntu Security Notices) on this machine." msgstr "" "Inspecionar e corrigir CVEs and USNs (Ubuntu Security Notices) nesta máquina." -#: ../../uaclient/messages/__init__.py:941 +#: ../../uaclient/messages/__init__.py:951 msgid "" "Security vulnerability ID to inspect and resolve on this system. Format: CVE-" "yyyy-nnnn, CVE-yyyy-nnnnnnn or USN-nnnn-dd" @@ -1749,7 +1787,7 @@ "ID da Vulnerabilidade de segurança para inspecionar e corrigir neste " "sistema. Formato: CVE-yyyy-nnnn, CVE-yyyy-nnnnnnn ou USN-nnnn-dd" -#: ../../uaclient/messages/__init__.py:945 +#: ../../uaclient/messages/__init__.py:955 msgid "" "If used, fix will not actually run but will display everything that will " "happen on the machine during the command." @@ -1757,7 +1795,7 @@ "Se usado, fix não vai executar modificações. Ao invés disso, irá mostrar " "tudo que irá acontecer na máquina durante a execução real do comando." -#: ../../uaclient/messages/__init__.py:950 +#: ../../uaclient/messages/__init__.py:960 msgid "" "If used, when fixing a USN, the command will not try to also fix related " "USNs to the target USN." @@ -1765,7 +1803,20 @@ "Se usado, ao corrigir uma USN, o comando não tentará corrigar as USN " "relacionadas à USN principal." -#: ../../uaclient/messages/__init__.py:955 +#: ../../uaclient/messages/__init__.py:965 +msgid "" +"WARNING: Failed to update ESM cache - package availability may be inaccurate" +msgstr "" + +#: ../../uaclient/messages/__init__.py:969 +#, python-brace-format +msgid "" +"{bold}WARNING: Unable to update ESM cache when running as non-root,\n" +"please run sudo apt update and try again if packages cannot be found." +"{end_bold}" +msgstr "" + +#: ../../uaclient/messages/__init__.py:975 msgid "" "Show security updates for packages in the system, including all\n" "available Expanded Security Maintenance (ESM) related content.\n" @@ -1802,23 +1853,23 @@ "completo\n" "a respeito dos serviços do Ubuntu Pro, execute 'pro status'.\n" -#: ../../uaclient/messages/__init__.py:975 +#: ../../uaclient/messages/__init__.py:995 msgid "List and present information about third-party packages" msgstr "Lista e mostra informações presentes sobre pacotes de terceiros" -#: ../../uaclient/messages/__init__.py:978 +#: ../../uaclient/messages/__init__.py:998 msgid "List and present information about unavailable packages" msgstr "Lista e mostra informações sobre pacotes indisponíveis" -#: ../../uaclient/messages/__init__.py:981 +#: ../../uaclient/messages/__init__.py:1001 msgid "List and present information about esm-infra packages" msgstr "Lista e mostra informações sobre pacotes esm-infra" -#: ../../uaclient/messages/__init__.py:984 +#: ../../uaclient/messages/__init__.py:1004 msgid "List and present information about esm-apps packages" msgstr "Lista e mostra informações sobre pacotes esm-apps" -#: ../../uaclient/messages/__init__.py:988 +#: ../../uaclient/messages/__init__.py:1008 msgid "" "Refresh three distinct Ubuntu Pro related artifacts in the system:\n" "\n" @@ -1842,75 +1893,75 @@ "especificada,\n" "todas as opções serão atualizadas.\n" -#: ../../uaclient/messages/__init__.py:1000 +#: ../../uaclient/messages/__init__.py:1020 msgid "Target to refresh." msgstr "Opção para atualizar" -#: ../../uaclient/messages/__init__.py:1003 +#: ../../uaclient/messages/__init__.py:1023 msgid "Detach this machine from an Ubuntu Pro subscription." msgstr "Desvincule esta máquina de uma assinatura do Ubuntu Pro." -#: ../../uaclient/messages/__init__.py:1007 +#: ../../uaclient/messages/__init__.py:1027 msgid "Provide detailed information about Ubuntu Pro services." msgstr "Providencia informações detalhadas sobre os serviços do Ubuntu Pro." -#: ../../uaclient/messages/__init__.py:1010 +#: ../../uaclient/messages/__init__.py:1030 #, python-brace-format msgid "a service to view help output for. One of: {options}" msgstr "serviço para qual informação de ajuda será mostrada. Um de: {options}" -#: ../../uaclient/messages/__init__.py:1012 +#: ../../uaclient/messages/__init__.py:1032 msgid "Include beta services" msgstr "Inclui os serviços beta" -#: ../../uaclient/messages/__init__.py:1014 +#: ../../uaclient/messages/__init__.py:1034 msgid "Enable an Ubuntu Pro service." msgstr "Habilite um serviço Ubuntu Pro." -#: ../../uaclient/messages/__init__.py:1016 +#: ../../uaclient/messages/__init__.py:1036 #, python-brace-format msgid "the name(s) of the Ubuntu Pro services to enable. One of: {options}" msgstr "os nome(s)n dos serviços Ubuntu Pro para habilitar. Um de: {options}" -#: ../../uaclient/messages/__init__.py:1019 +#: ../../uaclient/messages/__init__.py:1039 msgid "" "do not auto-install packages. Valid for cc-eal, cis and realtime-kernel." msgstr "" "não instale pacotes automaticamente. Válido para cc-eal, cis e realtime-" "kernel." -#: ../../uaclient/messages/__init__.py:1022 +#: ../../uaclient/messages/__init__.py:1042 msgid "allow beta service to be enabled" msgstr "permita que serviços beta sejam habilitados" -#: ../../uaclient/messages/__init__.py:1024 +#: ../../uaclient/messages/__init__.py:1044 msgid "The name of the variant to use when enabling the service" msgstr "O nome da variante para usar ao habilitar o serviço" -#: ../../uaclient/messages/__init__.py:1027 +#: ../../uaclient/messages/__init__.py:1047 msgid "Disable an Ubuntu Pro service." msgstr "Desabilite um serviço do Ubuntu Pro." -#: ../../uaclient/messages/__init__.py:1029 +#: ../../uaclient/messages/__init__.py:1049 #, python-brace-format msgid "the name(s) of the Ubuntu Pro services to disable. One of: {options}" msgstr "" "os nome(s) do serviços do Ubuntu Pro para desabilitar. Um de: {options}" -#: ../../uaclient/messages/__init__.py:1032 +#: ../../uaclient/messages/__init__.py:1052 msgid "" "disable the service and remove/downgrade related packages (experimental)" msgstr "" -#: ../../uaclient/messages/__init__.py:1036 +#: ../../uaclient/messages/__init__.py:1056 msgid "Output system related information related to Pro services" msgstr "Mostre informações de sistema relacionados aos serviços do Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:1038 +#: ../../uaclient/messages/__init__.py:1058 msgid "does the system need to be rebooted" msgstr "se o sistema precisa ser reiniciado" -#: ../../uaclient/messages/__init__.py:1040 +#: ../../uaclient/messages/__init__.py:1060 msgid "" "Report the current reboot-required status for the machine.\n" "\n" @@ -1939,7 +1990,7 @@ " reiniciada, mas você pode averiguar se o reinício pode acontecer no\n" " período de manutenção mais próximo.\n" -#: ../../uaclient/messages/__init__.py:1057 +#: ../../uaclient/messages/__init__.py:1077 msgid "" "Report current status of Ubuntu Pro services on system.\n" "\n" @@ -2011,80 +2062,80 @@ "Se a flag --all for usada, serviços beta e indisponíveis também\n" "serão listado.\n" -#: ../../uaclient/messages/__init__.py:1092 +#: ../../uaclient/messages/__init__.py:1112 msgid "Block waiting on pro to complete" msgstr "Espera até o pro completar sua operação" -#: ../../uaclient/messages/__init__.py:1094 +#: ../../uaclient/messages/__init__.py:1114 msgid "simulate the output status using a provided token" msgstr "simula a mensagem de status usando um token fornecido" -#: ../../uaclient/messages/__init__.py:1096 +#: ../../uaclient/messages/__init__.py:1116 msgid "Include unavailable and beta services" msgstr "Inclui serviços beta e indisponíveis" -#: ../../uaclient/messages/__init__.py:1098 +#: ../../uaclient/messages/__init__.py:1118 msgid "show all debug log messages to console" msgstr "mostra todas os logs de debug na saída do comando" -#: ../../uaclient/messages/__init__.py:1099 +#: ../../uaclient/messages/__init__.py:1119 #, python-brace-format msgid "show version of {name}" msgstr "mostra versão de {name}" -#: ../../uaclient/messages/__init__.py:1101 +#: ../../uaclient/messages/__init__.py:1121 msgid "attach this machine to an Ubuntu Pro subscription" msgstr "vincule esta máquina a uma assinatura Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:1104 +#: ../../uaclient/messages/__init__.py:1124 msgid "automatically attach on supported platforms" msgstr "automaticamente vincule está máquina em plataformas suportadas" -#: ../../uaclient/messages/__init__.py:1105 +#: ../../uaclient/messages/__init__.py:1125 msgid "collect Pro logs and debug information" msgstr "coleta logs do Pro e informações de debug" -#: ../../uaclient/messages/__init__.py:1106 +#: ../../uaclient/messages/__init__.py:1126 msgid "manage Ubuntu Pro configuration on this machine" msgstr "gerencia configuração do Ubuntu Pro nesta máquina" -#: ../../uaclient/messages/__init__.py:1108 +#: ../../uaclient/messages/__init__.py:1128 msgid "remove this machine from an Ubuntu Pro subscription" msgstr "desvincula esta máquina de uma assinatura Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:1111 +#: ../../uaclient/messages/__init__.py:1131 msgid "disable a specific Ubuntu Pro service on this machine" msgstr "desabilita nesta máquina um serviço do Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:1114 +#: ../../uaclient/messages/__init__.py:1134 msgid "enable a specific Ubuntu Pro service on this machine" msgstr "habilita nesta máquina um serviço Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:1117 +#: ../../uaclient/messages/__init__.py:1137 msgid "check for and mitigate the impact of a CVE/USN on this system" msgstr "checa e corrige os problemas de segurança de um CVE/USN na máquina" -#: ../../uaclient/messages/__init__.py:1120 +#: ../../uaclient/messages/__init__.py:1140 msgid "list available security updates for the system" msgstr "lista atualizações de segurança disponíveis para o sistema" -#: ../../uaclient/messages/__init__.py:1123 +#: ../../uaclient/messages/__init__.py:1143 msgid "show detailed information about Ubuntu Pro services" msgstr "mostra informações detalhadas sobre os serviços do Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:1125 +#: ../../uaclient/messages/__init__.py:1145 msgid "refresh Ubuntu Pro services" msgstr "atualiza os serviços do Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:1126 +#: ../../uaclient/messages/__init__.py:1146 msgid "current status of all Ubuntu Pro services" msgstr "status atual de todos os serviços do Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:1127 +#: ../../uaclient/messages/__init__.py:1147 msgid "show system information related to Pro services" msgstr "mostra informações de sistema relacionados a serviços do Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:1130 +#: ../../uaclient/messages/__init__.py:1150 #, python-brace-format msgid "" "WARNING: this output is intended to be human readable, and subject to " @@ -2102,15 +2153,15 @@ #. ############################################################################## #. SERVICE-SPECIFIC MESSAGES # #. ############################################################################## -#: ../../uaclient/messages/__init__.py:1143 +#: ../../uaclient/messages/__init__.py:1163 msgid "Anbox Cloud" msgstr "Anbox Cloud" -#: ../../uaclient/messages/__init__.py:1144 +#: ../../uaclient/messages/__init__.py:1164 msgid "Scalable Android in the cloud" msgstr "Android escalável na nuvem" -#: ../../uaclient/messages/__init__.py:1146 +#: ../../uaclient/messages/__init__.py:1166 #, python-brace-format msgid "" "Anbox Cloud lets you stream mobile apps securely, at any scale, to any " @@ -2128,7 +2179,7 @@ "{url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1157 +#: ../../uaclient/messages/__init__.py:1177 #, python-brace-format msgid "" "To finish setting up the Anbox Cloud Appliance, run:\n" @@ -2140,15 +2191,15 @@ "For more information, see {url}\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1168 +#: ../../uaclient/messages/__init__.py:1188 msgid "CC EAL2" msgstr "" -#: ../../uaclient/messages/__init__.py:1169 +#: ../../uaclient/messages/__init__.py:1189 msgid "Common Criteria EAL2 Provisioning Packages" msgstr "Pacotes de Provisionamento do Common Criteria EAL2" -#: ../../uaclient/messages/__init__.py:1171 +#: ../../uaclient/messages/__init__.py:1191 msgid "" "Common Criteria is an Information Technology Security Evaluation standard\n" "(ISO/IEC IS 15408) for computer security certification. Ubuntu 16.04 has " @@ -2158,29 +2209,29 @@ "on Intel x86_64, IBM Power8 and IBM Z hardware platforms." msgstr "" -#: ../../uaclient/messages/__init__.py:1178 +#: ../../uaclient/messages/__init__.py:1198 msgid "" "(This will download more than 500MB of packages, so may take some time.)" msgstr "" -#: ../../uaclient/messages/__init__.py:1182 +#: ../../uaclient/messages/__init__.py:1202 #, python-brace-format msgid "Please follow instructions in {filename} to configure EAL2" msgstr "" -#: ../../uaclient/messages/__init__.py:1185 +#: ../../uaclient/messages/__init__.py:1205 msgid "CIS Audit" msgstr "" -#: ../../uaclient/messages/__init__.py:1186 +#: ../../uaclient/messages/__init__.py:1206 msgid "Ubuntu Security Guide" msgstr "" -#: ../../uaclient/messages/__init__.py:1187 +#: ../../uaclient/messages/__init__.py:1207 msgid "Security compliance and audit tools" msgstr "Ferramentas de auditoria e conformidade de segurança" -#: ../../uaclient/messages/__init__.py:1189 +#: ../../uaclient/messages/__init__.py:1209 #, python-brace-format msgid "" "Ubuntu Security Guide is a tool for hardening and auditing and allows for\n" @@ -2190,17 +2241,17 @@ "{url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1195 +#: ../../uaclient/messages/__init__.py:1215 #, python-brace-format msgid "Visit {url} to learn how to use CIS" msgstr "" -#: ../../uaclient/messages/__init__.py:1198 +#: ../../uaclient/messages/__init__.py:1218 #, python-brace-format msgid "Visit {url} for the next steps" msgstr "" -#: ../../uaclient/messages/__init__.py:1202 +#: ../../uaclient/messages/__init__.py:1222 #, python-brace-format msgid "" "From Ubuntu 20.04 onward 'pro enable cis' has been\n" @@ -2208,11 +2259,11 @@ "{url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1210 +#: ../../uaclient/messages/__init__.py:1230 msgid "Expanded Security Maintenance for Applications" msgstr "Manutenção de Segurança Expandida para Aplicações" -#: ../../uaclient/messages/__init__.py:1213 +#: ../../uaclient/messages/__init__.py:1233 #, python-brace-format msgid "" "Expanded Security Maintenance for Applications is enabled by default on\n" @@ -2224,11 +2275,11 @@ "{url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1224 +#: ../../uaclient/messages/__init__.py:1244 msgid "Expanded Security Maintenance for Infrastructure" msgstr "Manutenção de Segurança Expandida para Infraestrutura" -#: ../../uaclient/messages/__init__.py:1227 +#: ../../uaclient/messages/__init__.py:1247 #, python-brace-format msgid "" "Expanded Security Maintenance for Infrastructure provides access to a " @@ -2241,15 +2292,15 @@ "{url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1236 +#: ../../uaclient/messages/__init__.py:1256 msgid "FIPS" msgstr "" -#: ../../uaclient/messages/__init__.py:1237 +#: ../../uaclient/messages/__init__.py:1257 msgid "NIST-certified FIPS crypto packages" msgstr "Pacotes FIPS de criptografia certificados pelo NIST" -#: ../../uaclient/messages/__init__.py:1239 +#: ../../uaclient/messages/__init__.py:1259 #, python-brace-format msgid "" "Installs FIPS 140 crypto packages for FedRAMP, FISMA and compliance use " @@ -2260,18 +2311,18 @@ "choose \"fips-updates\" for maximum security. Find out more at {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1246 +#: ../../uaclient/messages/__init__.py:1266 msgid "Could not determine cloud, defaulting to generic FIPS package." msgstr "" -#: ../../uaclient/messages/__init__.py:1249 +#: ../../uaclient/messages/__init__.py:1269 #, python-brace-format msgid "" "FIPS kernel is running in a disabled state.\n" " To manually remove fips kernel: {url}\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1255 +#: ../../uaclient/messages/__init__.py:1275 msgid "" "Warning: FIPS kernel is not optimized for your specific cloud.\n" "To fix it, run the following commands:\n" @@ -2282,20 +2333,20 @@ " 4. sudo reboot\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1267 +#: ../../uaclient/messages/__init__.py:1287 msgid "" "This will install the FIPS packages. The Livepatch service will be " "unavailable.\n" "Warning: This action can take some time and cannot be undone.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1276 +#: ../../uaclient/messages/__init__.py:1296 msgid "" "This will install the FIPS packages including security updates.\n" "Warning: This action can take some time and cannot be undone.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1285 +#: ../../uaclient/messages/__init__.py:1305 #, python-brace-format msgid "" "Warning: Enabling {title} in a container.\n" @@ -2305,53 +2356,63 @@ "Warning: This action can take some time and cannot be undone.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1297 +#: ../../uaclient/messages/__init__.py:1317 #, python-brace-format msgid "" "This will disable the {title} entitlement but the {title} packages will " "remain installed.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1304 +#: ../../uaclient/messages/__init__.py:1324 +#, python-brace-format +msgid "" +"This will downgrade the kernel from {current_version} to {new_version}.\n" +"Warning: Downgrading the kernel may cause hardware failures. Please ensure " +"the\n" +" hardware is compatible with the new kernel version before " +"proceeding.\n" +msgstr "" + +#: ../../uaclient/messages/__init__.py:1331 msgid "FIPS support requires system reboot to complete configuration." msgstr "" -#: ../../uaclient/messages/__init__.py:1306 -#: ../../uaclient/messages/__init__.py:1755 +#: ../../uaclient/messages/__init__.py:1333 +#: ../../uaclient/messages/__init__.py:1766 msgid "Reboot to FIPS kernel required" msgstr "" -#: ../../uaclient/messages/__init__.py:1308 +#: ../../uaclient/messages/__init__.py:1335 msgid "This FIPS install is out of date, run: sudo pro enable fips" msgstr "" -#: ../../uaclient/messages/__init__.py:1311 +#: ../../uaclient/messages/__init__.py:1338 msgid "Disabling FIPS requires system reboot to complete operation." msgstr "" -#: ../../uaclient/messages/__init__.py:1314 +#: ../../uaclient/messages/__init__.py:1341 #, python-brace-format msgid "{service} {pkg} package could not be installed" msgstr "" -#: ../../uaclient/messages/__init__.py:1317 +#: ../../uaclient/messages/__init__.py:1344 msgid "" "Please run `apt upgrade` to ensure all FIPS packages are updated to the " "correct\n" "version.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1323 +#: ../../uaclient/messages/__init__.py:1350 msgid "FIPS Updates" msgstr "" -#: ../../uaclient/messages/__init__.py:1325 +#: ../../uaclient/messages/__init__.py:1352 msgid "FIPS compliant crypto packages with stable security updates" msgstr "" "Pacotes de criptografia compatíveis com FIPS com atualizações de segurança " "estáveis" -#: ../../uaclient/messages/__init__.py:1328 +#: ../../uaclient/messages/__init__.py:1355 #, python-brace-format msgid "" "fips-updates installs FIPS 140 crypto packages including all security " @@ -2360,22 +2421,22 @@ "You can find out more at {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1334 +#: ../../uaclient/messages/__init__.py:1361 msgid "FIPS Preview" msgstr "" -#: ../../uaclient/messages/__init__.py:1336 +#: ../../uaclient/messages/__init__.py:1363 msgid "Preview of FIPS crypto packages undergoing certification with NIST" msgstr "" "Prévia de pacotes FIPS de criptografia em processo de certificação pelo NIST" -#: ../../uaclient/messages/__init__.py:1339 +#: ../../uaclient/messages/__init__.py:1366 msgid "" "Installs FIPS crypto packages that are under certification with NIST,\n" "for FedRAMP, FISMA and compliance use cases." msgstr "" -#: ../../uaclient/messages/__init__.py:1344 +#: ../../uaclient/messages/__init__.py:1371 msgid "" "This will install crypto packages that have been submitted to NIST for " "review\n" @@ -2387,15 +2448,15 @@ "Warning: This action can take some time and cannot be undone.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1355 +#: ../../uaclient/messages/__init__.py:1382 msgid "Landscape" msgstr "" -#: ../../uaclient/messages/__init__.py:1357 +#: ../../uaclient/messages/__init__.py:1384 msgid "Management and administration tool for Ubuntu" msgstr "Ferramenta de gerenciamento e administração para o Ubuntu" -#: ../../uaclient/messages/__init__.py:1360 +#: ../../uaclient/messages/__init__.py:1387 #, python-brace-format msgid "" "Landscape Client can be installed on this machine and enrolled in " @@ -2408,22 +2469,22 @@ "more. Find out more about Landscape at {home_url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1373 +#: ../../uaclient/messages/__init__.py:1400 msgid "" "/etc/landscape/client.conf contains your landscape-client configuration.\n" "To re-enable Landscape with the same configuration, run:\n" " sudo pro enable landscape --assume-yes\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1380 +#: ../../uaclient/messages/__init__.py:1407 msgid "Livepatch" msgstr "" -#: ../../uaclient/messages/__init__.py:1381 +#: ../../uaclient/messages/__init__.py:1408 msgid "Canonical Livepatch service" msgstr "Serviço de Livepatch da Canonical" -#: ../../uaclient/messages/__init__.py:1383 +#: ../../uaclient/messages/__init__.py:1410 #, python-brace-format msgid "" "Livepatch provides selected high and critical kernel CVE fixes and other\n" @@ -2438,42 +2499,50 @@ "service at {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1392 +#: ../../uaclient/messages/__init__.py:1419 msgid "Current kernel is not supported" msgstr "" -#: ../../uaclient/messages/__init__.py:1395 +#: ../../uaclient/messages/__init__.py:1422 #, python-brace-format msgid "Supported livepatch kernels are listed here: {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1398 +#: ../../uaclient/messages/__init__.py:1425 #, python-brace-format msgid "Unable to configure livepatch: {error_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:1400 +#: ../../uaclient/messages/__init__.py:1427 msgid "Unable to enable Livepatch: " msgstr "" -#: ../../uaclient/messages/__init__.py:1402 +#: ../../uaclient/messages/__init__.py:1429 msgid "Disabling Livepatch prior to re-attach with new token" msgstr "" -#: ../../uaclient/messages/__init__.py:1405 +#: ../../uaclient/messages/__init__.py:1432 msgid "Livepatch support requires a system reboot across LTS upgrade." msgstr "" -#: ../../uaclient/messages/__init__.py:1408 -#: ../../uaclient/messages/__init__.py:1421 +#: ../../uaclient/messages/__init__.py:1434 +msgid "Installing Livepatch" +msgstr "" + +#: ../../uaclient/messages/__init__.py:1435 +msgid "Setting up Livepatch" +msgstr "" + +#: ../../uaclient/messages/__init__.py:1437 +#: ../../uaclient/messages/__init__.py:1450 msgid "Real-time kernel" msgstr "" -#: ../../uaclient/messages/__init__.py:1410 +#: ../../uaclient/messages/__init__.py:1439 msgid "Ubuntu kernel with PREEMPT_RT patches integrated" msgstr "Kernel do Ubuntu com patches PREEMPT_RT integrados" -#: ../../uaclient/messages/__init__.py:1413 +#: ../../uaclient/messages/__init__.py:1442 msgid "" "The Real-time kernel is an Ubuntu kernel with PREEMPT_RT patches integrated. " "It\n" @@ -2486,35 +2555,35 @@ "Livepatch." msgstr "" -#: ../../uaclient/messages/__init__.py:1423 +#: ../../uaclient/messages/__init__.py:1452 msgid "Generic version of the RT kernel (default)" msgstr "Versão genérica do kernel RT (padrão)" -#: ../../uaclient/messages/__init__.py:1425 +#: ../../uaclient/messages/__init__.py:1454 msgid "Real-time NVIDIA Tegra Kernel" msgstr "" -#: ../../uaclient/messages/__init__.py:1427 +#: ../../uaclient/messages/__init__.py:1456 msgid "RT kernel optimized for NVIDIA Tegra platform" msgstr "Kernel RT otimizado para a plataforma NVIDIA Tegra" -#: ../../uaclient/messages/__init__.py:1429 +#: ../../uaclient/messages/__init__.py:1458 msgid "Raspberry Pi Real-time for Pi5/Pi4" msgstr "" -#: ../../uaclient/messages/__init__.py:1431 +#: ../../uaclient/messages/__init__.py:1460 msgid "24.04 Real-time kernel optimised for Raspberry Pi" msgstr "" -#: ../../uaclient/messages/__init__.py:1433 +#: ../../uaclient/messages/__init__.py:1462 msgid "Real-time Intel IOTG Kernel" msgstr "" -#: ../../uaclient/messages/__init__.py:1435 +#: ../../uaclient/messages/__init__.py:1464 msgid "RT kernel optimized for Intel IOTG platform" msgstr "Kernel RT otimizado para a plataforma Intel IOTG" -#: ../../uaclient/messages/__init__.py:1438 +#: ../../uaclient/messages/__init__.py:1467 #, python-brace-format msgid "" "The Real-time kernel is an Ubuntu kernel with PREEMPT_RT patches " @@ -2527,7 +2596,7 @@ "Do you want to continue? [ default = Yes ]: (Y/n) " msgstr "" -#: ../../uaclient/messages/__init__.py:1449 +#: ../../uaclient/messages/__init__.py:1478 msgid "" "This will remove the boot order preference for the Real-time kernel and\n" "disable updates to the Real-time kernel.\n" @@ -2544,15 +2613,15 @@ "Are you sure? (y/N) " msgstr "" -#: ../../uaclient/messages/__init__.py:1465 +#: ../../uaclient/messages/__init__.py:1494 msgid "ROS ESM Security Updates" msgstr "" -#: ../../uaclient/messages/__init__.py:1466 +#: ../../uaclient/messages/__init__.py:1495 msgid "Security Updates for the Robot Operating System" msgstr "Atualizações de Segurança para o Robot Operating System" -#: ../../uaclient/messages/__init__.py:1468 +#: ../../uaclient/messages/__init__.py:1497 #, python-brace-format msgid "" "ros provides access to a private PPA which includes security-related " @@ -2565,15 +2634,15 @@ "{url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1477 +#: ../../uaclient/messages/__init__.py:1506 msgid "ROS ESM All Updates" msgstr "" -#: ../../uaclient/messages/__init__.py:1479 +#: ../../uaclient/messages/__init__.py:1508 msgid "All Updates for the Robot Operating System" msgstr "Todas as Atualizações para o Robot Operating System" -#: ../../uaclient/messages/__init__.py:1482 +#: ../../uaclient/messages/__init__.py:1511 #, python-brace-format msgid "" "ros-updates provides access to a private PPA that includes non-security-" @@ -2586,14 +2655,15 @@ "{url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1553 +#: ../../uaclient/messages/__init__.py:1582 +#, python-brace-format msgid "" -"Unexpected error(s) occurred.\n" -"For more details, see the log: /var/log/ubuntu-advantage.log\n" -"To file a bug run: ubuntu-bug ubuntu-advantage-tools" +"An unexpected error occurred: {error_msg}\n" +"For more details, see the log: {log_path}\n" +"If you think this is a bug, please run: ubuntu-bug ubuntu-advantage-tools" msgstr "" -#: ../../uaclient/messages/__init__.py:1563 +#: ../../uaclient/messages/__init__.py:1592 #, python-brace-format msgid "" "Failed to access URL: {url}\n" @@ -2601,7 +2671,7 @@ "Please install \"ca-certificates\" and try again." msgstr "" -#: ../../uaclient/messages/__init__.py:1573 +#: ../../uaclient/messages/__init__.py:1602 #, python-brace-format msgid "" "Failed to access URL: {url}\n" @@ -2609,303 +2679,293 @@ "Please check your openssl configuration." msgstr "" -#: ../../uaclient/messages/__init__.py:1582 +#: ../../uaclient/messages/__init__.py:1611 #, python-brace-format msgid "Ignoring unknown argument '{arg}'" msgstr "" -#: ../../uaclient/messages/__init__.py:1588 +#: ../../uaclient/messages/__init__.py:1617 #, python-brace-format msgid "" "A new version of the client is available: {version}. Please upgrade to the " "latest version to get the new features and bug fixes." msgstr "" -#: ../../uaclient/messages/__init__.py:1595 +#: ../../uaclient/messages/__init__.py:1624 #, python-brace-format msgid "{title} does not support being enabled with --access-only" msgstr "" -#: ../../uaclient/messages/__init__.py:1600 +#: ../../uaclient/messages/__init__.py:1629 #, python-brace-format msgid "{title} does not support being disabled with --purge" msgstr "" -#: ../../uaclient/messages/__init__.py:1606 +#: ../../uaclient/messages/__init__.py:1635 #, python-brace-format msgid "Cannot disable dependent service: {required_service}{error}" msgstr "" -#: ../../uaclient/messages/__init__.py:1613 -#, python-brace-format -msgid "" -"Cannot disable {service_being_disabled} when {dependent_service} is " -"enabled.\n" -msgstr "" - -#: ../../uaclient/messages/__init__.py:1621 +#: ../../uaclient/messages/__init__.py:1642 #, python-brace-format msgid "Cannot disable {entitlement_name} with purge: no origin value defined" msgstr "" -#: ../../uaclient/messages/__init__.py:1628 +#: ../../uaclient/messages/__init__.py:1649 #, python-brace-format msgid "Cannot enable required service: {service}{error}" msgstr "" -#: ../../uaclient/messages/__init__.py:1633 -#, python-brace-format -msgid "" -"Cannot enable {service_being_enabled} when {required_service} is disabled.\n" -msgstr "" - -#: ../../uaclient/messages/__init__.py:1641 -#, python-brace-format -msgid "" -"Cannot enable {service_being_enabled} when {incompatible_service} is enabled." -msgstr "" - -#: ../../uaclient/messages/__init__.py:1649 +#: ../../uaclient/messages/__init__.py:1654 #, python-brace-format msgid "Cannot install {title} on a container." msgstr "" -#: ../../uaclient/messages/__init__.py:1652 +#: ../../uaclient/messages/__init__.py:1657 #, python-brace-format msgid "{title} is not configured" msgstr "" -#: ../../uaclient/messages/__init__.py:1657 +#: ../../uaclient/messages/__init__.py:1662 #, python-brace-format msgid "" "The {service} service is not enabled because the {package} package is\n" "not installed." msgstr "" -#: ../../uaclient/messages/__init__.py:1663 +#: ../../uaclient/messages/__init__.py:1668 #, python-brace-format msgid "{title} is active" msgstr "" -#: ../../uaclient/messages/__init__.py:1667 +#: ../../uaclient/messages/__init__.py:1672 #, python-brace-format msgid "{title} does not have an aptURL directive" msgstr "" -#: ../../uaclient/messages/__init__.py:1672 +#: ../../uaclient/messages/__init__.py:1676 +#, python-brace-format +msgid "{title} does not have a suites directive" +msgstr "" + +#: ../../uaclient/messages/__init__.py:1681 #, python-brace-format msgid "" -"{title} is not currently enabled\n" +"{title} is not currently enabled - nothing to do.\n" "See: sudo pro status" msgstr "" -#: ../../uaclient/messages/__init__.py:1679 +#: ../../uaclient/messages/__init__.py:1689 #, python-brace-format msgid "" "Disabling {title} with pro is not supported.\n" "See: sudo pro status" msgstr "" -#: ../../uaclient/messages/__init__.py:1686 +#: ../../uaclient/messages/__init__.py:1696 #, python-brace-format msgid "" -"{title} is already enabled.\n" +"{title} is already enabled - nothing to do.\n" "See: sudo pro status" msgstr "" -#: ../../uaclient/messages/__init__.py:1693 +#: ../../uaclient/messages/__init__.py:1704 #, python-brace-format msgid "" "This subscription is not entitled to {{title}}\n" "View your subscription at: {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1699 +#: ../../uaclient/messages/__init__.py:1710 #, python-brace-format msgid "{title} is not entitled" msgstr "" -#: ../../uaclient/messages/__init__.py:1705 +#: ../../uaclient/messages/__init__.py:1716 #, python-brace-format msgid "" "{title} is not available for kernel {kernel}.\n" "Minimum kernel version required: {min_kernel}." msgstr "" -#: ../../uaclient/messages/__init__.py:1713 +#: ../../uaclient/messages/__init__.py:1724 #, python-brace-format msgid "" "{title} is not available for kernel {kernel}.\n" "Supported flavors are: {supported_kernels}." msgstr "" -#: ../../uaclient/messages/__init__.py:1721 +#: ../../uaclient/messages/__init__.py:1732 #, python-brace-format msgid "{title} is not available for Ubuntu {series}." msgstr "" -#: ../../uaclient/messages/__init__.py:1728 +#: ../../uaclient/messages/__init__.py:1739 #, python-brace-format msgid "" "{title} is not available for platform {arch}.\n" "Supported platforms are: {supported_arches}." msgstr "" -#: ../../uaclient/messages/__init__.py:1736 +#: ../../uaclient/messages/__init__.py:1747 #, python-brace-format msgid "" "{title} is not available for CPU vendor {vendor}.\n" "Supported CPU vendors are: {supported_vendors}." msgstr "" -#: ../../uaclient/messages/__init__.py:1743 +#: ../../uaclient/messages/__init__.py:1754 msgid "no entitlement affordances checked" msgstr "" -#: ../../uaclient/messages/__init__.py:1749 +#: ../../uaclient/messages/__init__.py:1760 #, python-brace-format msgid "" "Ubuntu {{series}} does not provide {{cloud}} optimized FIPS kernel\n" "For help see: {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1759 +#: ../../uaclient/messages/__init__.py:1770 #, python-brace-format msgid "Cannot enable {fips} when {fips_updates} is enabled." msgstr "" -#: ../../uaclient/messages/__init__.py:1762 +#: ../../uaclient/messages/__init__.py:1773 #, python-brace-format msgid "{file_name} is not set to 1" msgstr "" -#: ../../uaclient/messages/__init__.py:1766 +#: ../../uaclient/messages/__init__.py:1777 #, python-brace-format msgid "Cannot enable {fips} because {fips_updates} was once enabled." msgstr "" -#: ../../uaclient/messages/__init__.py:1771 +#: ../../uaclient/messages/__init__.py:1782 msgid "" "FIPS cannot be enabled if FIPS Updates has ever been enabled because FIPS " "Updates installs security patches that aren't officially certified." msgstr "" -#: ../../uaclient/messages/__init__.py:1779 +#: ../../uaclient/messages/__init__.py:1790 msgid "" "FIPS Updates cannot be enabled if FIPS is enabled. FIPS Updates installs " "security patches that aren't officially certified." msgstr "" -#: ../../uaclient/messages/__init__.py:1788 +#: ../../uaclient/messages/__init__.py:1799 msgid "" "Livepatch cannot be enabled while running the official FIPS certified " "kernel. If you would like a FIPS compliant kernel with additional bug fixes " "and security updates, you can use the FIPS Updates service with Livepatch." msgstr "" -#: ../../uaclient/messages/__init__.py:1796 +#: ../../uaclient/messages/__init__.py:1807 msgid "canonical-livepatch snap is not installed." msgstr "" -#: ../../uaclient/messages/__init__.py:1800 +#: ../../uaclient/messages/__init__.py:1811 msgid "Cannot enable Livepatch when FIPS is enabled." msgstr "" -#: ../../uaclient/messages/__init__.py:1805 +#: ../../uaclient/messages/__init__.py:1816 msgid "" "The running kernel has reached the end of its active livepatch window.\n" "Please upgrade the kernel with apt and reboot for continued livepatch " "support." msgstr "" -#: ../../uaclient/messages/__init__.py:1813 +#: ../../uaclient/messages/__init__.py:1824 #, python-brace-format msgid "" "The current kernel ({{version}}, {{arch}}) has reached the end of its " "livepatch support.\n" "Supported kernels are listed here: {url}\n" -"Either switch to a supported kernel or `pro disable livepatch` to dismiss " -"this warning." +"Either switch to a supported kernel or `sudo pro disable livepatch` to " +"dismiss this warning." msgstr "" -#: ../../uaclient/messages/__init__.py:1822 +#: ../../uaclient/messages/__init__.py:1833 #, python-brace-format msgid "" "The current kernel ({{version}}, {{arch}}) is not supported by livepatch.\n" "Supported kernels are listed here: {url}\n" -"Either switch to a supported kernel or `pro disable livepatch` to dismiss " -"this warning." +"Either switch to a supported kernel or `sudo pro disable livepatch` to " +"dismiss this warning." msgstr "" -#: ../../uaclient/messages/__init__.py:1832 +#: ../../uaclient/messages/__init__.py:1843 msgid "canonical-livepatch status didn't finish successfully" msgstr "" -#: ../../uaclient/messages/__init__.py:1838 +#: ../../uaclient/messages/__init__.py:1849 #, python-brace-format msgid "" "Error running canonical-livepatch status:\n" "{livepatch_error}" msgstr "" -#: ../../uaclient/messages/__init__.py:1847 +#: ../../uaclient/messages/__init__.py:1858 msgid "" "Realtime and FIPS require different kernels, so you cannot enable both at " "the same time." msgstr "" -#: ../../uaclient/messages/__init__.py:1854 +#: ../../uaclient/messages/__init__.py:1865 msgid "" "Realtime and FIPS Updates require different kernels, so you cannot enable " "both at the same time." msgstr "" -#: ../../uaclient/messages/__init__.py:1861 +#: ../../uaclient/messages/__init__.py:1872 msgid "Livepatch is not currently supported for the Real-time kernel." msgstr "" -#: ../../uaclient/messages/__init__.py:1866 +#: ../../uaclient/messages/__init__.py:1877 #, python-brace-format msgid "{service} cannot be enabled together with {variant}" msgstr "" -#: ../../uaclient/messages/__init__.py:1870 +#: ../../uaclient/messages/__init__.py:1881 msgid "Cannot install Real-time kernel on a container." msgstr "" -#: ../../uaclient/messages/__init__.py:1875 +#: ../../uaclient/messages/__init__.py:1886 +msgid "ROS packages assume ESM updates are enabled." +msgstr "" + +#: ../../uaclient/messages/__init__.py:1891 +msgid "ROS bug-fix updates assume ROS security fix updates are enabled." +msgstr "" + +#: ../../uaclient/messages/__init__.py:1897 msgid "apt-daily.timer jobs are not running" msgstr "" -#: ../../uaclient/messages/__init__.py:1879 +#: ../../uaclient/messages/__init__.py:1901 #, python-brace-format msgid "{cfg_name} is empty" msgstr "" -#: ../../uaclient/messages/__init__.py:1883 +#: ../../uaclient/messages/__init__.py:1905 #, python-brace-format msgid "{cfg_name} is turned off" msgstr "" -#: ../../uaclient/messages/__init__.py:1887 +#: ../../uaclient/messages/__init__.py:1909 msgid "unattended-upgrades package is not installed" msgstr "O pacote unattended-upgrades não está instalado" -#: ../../uaclient/messages/__init__.py:1893 +#: ../../uaclient/messages/__init__.py:1915 msgid "" "Landscape is installed and configured but not registered.\n" "Run `sudo landscape-config` to register, or run `sudo pro disable landscape`" msgstr "" -#: ../../uaclient/messages/__init__.py:1902 +#: ../../uaclient/messages/__init__.py:1924 msgid "landscape-client is either not installed or installed but disabled." msgstr "" -#: ../../uaclient/messages/__init__.py:1907 -msgid "landscape-config command failed" -msgstr "" - -#: ../../uaclient/messages/__init__.py:1913 +#: ../../uaclient/messages/__init__.py:1931 #, python-brace-format msgid "" "Error: issue \"{issue_id}\" is not recognized.\n" @@ -2915,28 +2975,28 @@ "USNs should follow the pattern USN-nnnn." msgstr "" -#: ../../uaclient/messages/__init__.py:1927 +#: ../../uaclient/messages/__init__.py:1950 msgid "Another process is running APT." msgstr "" -#: ../../uaclient/messages/__init__.py:1933 +#: ../../uaclient/messages/__init__.py:1956 #, python-brace-format msgid "" "APT update failed to read APT config for the following:\n" "{failed_repos}" msgstr "" -#: ../../uaclient/messages/__init__.py:1963 +#: ../../uaclient/messages/__init__.py:1986 #, python-brace-format msgid "Invalid APT credentials provided for {repo}" msgstr "" -#: ../../uaclient/messages/__init__.py:1968 +#: ../../uaclient/messages/__init__.py:1991 #, python-brace-format msgid "Timeout trying to access APT repository at {repo}" msgstr "" -#: ../../uaclient/messages/__init__.py:1974 +#: ../../uaclient/messages/__init__.py:1997 #, python-brace-format msgid "" "Unexpected APT error.\n" @@ -2944,107 +3004,107 @@ "See /var/log/ubuntu-advantage.log" msgstr "" -#: ../../uaclient/messages/__init__.py:1984 +#: ../../uaclient/messages/__init__.py:2007 #, python-brace-format msgid "" "Cannot validate credentials for APT repo. Timeout after {seconds} seconds " "trying to reach {repo}." msgstr "" -#: ../../uaclient/messages/__init__.py:1991 +#: ../../uaclient/messages/__init__.py:2014 #, python-brace-format msgid "snap {snap} is not installed or doesn't exist" msgstr "" -#: ../../uaclient/messages/__init__.py:1996 +#: ../../uaclient/messages/__init__.py:2019 #, python-brace-format msgid "" "Unexpected SNAPD API error\n" "{error}" msgstr "" -#: ../../uaclient/messages/__init__.py:2000 +#: ../../uaclient/messages/__init__.py:2023 msgid "Could not reach the SNAPD API" msgstr "" -#: ../../uaclient/messages/__init__.py:2004 +#: ../../uaclient/messages/__init__.py:2027 msgid "Failed to install snapd on the system" msgstr "" -#: ../../uaclient/messages/__init__.py:2009 +#: ../../uaclient/messages/__init__.py:2032 #, python-brace-format msgid "Unable to install Livepatch client: {error_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2014 +#: ../../uaclient/messages/__init__.py:2037 #, python-brace-format msgid "\"{proxy}\" is not working. Not setting as proxy." msgstr "" -#: ../../uaclient/messages/__init__.py:2019 +#: ../../uaclient/messages/__init__.py:2042 #, python-brace-format msgid "\"{proxy}\" is not a valid url. Not setting as proxy." msgstr "" -#: ../../uaclient/messages/__init__.py:2025 +#: ../../uaclient/messages/__init__.py:2048 msgid "" "To use an HTTPS proxy for HTTPS connections, please install pycurl with `apt " "install python3-pycurl`" msgstr "" -#: ../../uaclient/messages/__init__.py:2031 +#: ../../uaclient/messages/__init__.py:2054 #, python-brace-format msgid "PycURL Error: {e}" msgstr "" -#: ../../uaclient/messages/__init__.py:2035 +#: ../../uaclient/messages/__init__.py:2058 msgid "Proxy authentication failed" msgstr "" -#: ../../uaclient/messages/__init__.py:2041 +#: ../../uaclient/messages/__init__.py:2064 #, python-brace-format msgid "" "Failed to connect to {url}\n" "{cause_error}\n" msgstr "" -#: ../../uaclient/messages/__init__.py:2049 +#: ../../uaclient/messages/__init__.py:2072 #, python-brace-format msgid "Error connecting to {url}: {code} {body}" msgstr "" -#: ../../uaclient/messages/__init__.py:2055 +#: ../../uaclient/messages/__init__.py:2078 #, python-brace-format msgid "" "Cannot {operation} unknown service '{invalid_service}'.\n" "{service_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2064 +#: ../../uaclient/messages/__init__.py:2087 #, python-brace-format msgid "" "This machine is already attached to '{account_name}'\n" "To use a different subscription first run: sudo pro detach." msgstr "" -#: ../../uaclient/messages/__init__.py:2071 +#: ../../uaclient/messages/__init__.py:2094 #, python-brace-format msgid "Failed to attach machine. See {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2078 +#: ../../uaclient/messages/__init__.py:2101 #, python-brace-format msgid "" "Error while reading {config_name}:\n" "{error}" msgstr "" -#: ../../uaclient/messages/__init__.py:2083 +#: ../../uaclient/messages/__init__.py:2106 #, python-brace-format msgid "Invalid token. See {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2089 +#: ../../uaclient/messages/__init__.py:2112 #, python-brace-format msgid "" "Attach denied:\n" @@ -3052,7 +3112,7 @@ "Visit {url} to manage contract tokens." msgstr "" -#: ../../uaclient/messages/__init__.py:2099 +#: ../../uaclient/messages/__init__.py:2122 #, python-brace-format msgid "" "Attach denied:\n" @@ -3060,7 +3120,7 @@ "Visit {url} to manage contract tokens." msgstr "" -#: ../../uaclient/messages/__init__.py:2109 +#: ../../uaclient/messages/__init__.py:2132 #, python-brace-format msgid "" "Attach denied:\n" @@ -3068,101 +3128,132 @@ "Visit {url} to manage contract tokens." msgstr "" -#: ../../uaclient/messages/__init__.py:2119 +#: ../../uaclient/messages/__init__.py:2142 #, python-brace-format msgid "Expired token or contract. To obtain a new token visit: {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2126 +#: ../../uaclient/messages/__init__.py:2149 msgid "The magic attach token is already activated." msgstr "" -#: ../../uaclient/messages/__init__.py:2132 +#: ../../uaclient/messages/__init__.py:2155 msgid "The magic attach token is invalid, has expired or never existed" msgstr "" -#: ../../uaclient/messages/__init__.py:2138 +#: ../../uaclient/messages/__init__.py:2161 msgid "Service unavailable, please try again later." msgstr "" -#: ../../uaclient/messages/__init__.py:2143 +#: ../../uaclient/messages/__init__.py:2166 #, python-brace-format msgid "This attach flow does not support {param} with value: {value}" msgstr "" -#: ../../uaclient/messages/__init__.py:2149 +#: ../../uaclient/messages/__init__.py:2172 #, python-brace-format msgid "Ubuntu Pro server provided no aptURL directive for {entitlement_name}" msgstr "" -#: ../../uaclient/messages/__init__.py:2157 +#: ../../uaclient/messages/__init__.py:2180 #, python-brace-format msgid "" "This machine is not attached to an Ubuntu Pro subscription.\n" "See {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2166 +#: ../../uaclient/messages/__init__.py:2189 #, python-brace-format msgid "" -"To use '{{valid_service}}' you need an Ubuntu Pro subscription\n" -"Personal and community subscriptions are available at no charge\n" +"Cannot {{operation}} services when unattached - nothing to do.\n" +"To use '{{valid_service}}' you need an Ubuntu Pro subscription.\n" +"Personal and community subscriptions are available at no charge.\n" "See {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2182 +#: ../../uaclient/messages/__init__.py:2206 #, python-brace-format msgid "could not find entitlement named \"{entitlement_name}\"" msgstr "" -#: ../../uaclient/messages/__init__.py:2187 +#: ../../uaclient/messages/__init__.py:2211 msgid "failed to enable some services" msgstr "" -#: ../../uaclient/messages/__init__.py:2193 +#: ../../uaclient/messages/__init__.py:2216 +#, python-brace-format +msgid "failed to enable {service}" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2221 +#, python-brace-format +msgid "failed to disable {service}" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2227 msgid "Failed to enable default services, check: sudo pro status" msgstr "" -#: ../../uaclient/messages/__init__.py:2201 +#: ../../uaclient/messages/__init__.py:2235 msgid "Something went wrong during the attach process. Check the logs." msgstr "" -#: ../../uaclient/messages/__init__.py:2209 +#: ../../uaclient/messages/__init__.py:2243 #, python-brace-format msgid "Ubuntu Pro server provided no aptKey directive for {entitlement_name}" msgstr "" -#: ../../uaclient/messages/__init__.py:2216 +#: ../../uaclient/messages/__init__.py:2250 #, python-brace-format msgid "Ubuntu Pro server provided no suites directive for {entitlement_name}" msgstr "" -#: ../../uaclient/messages/__init__.py:2223 +#: ../../uaclient/messages/__init__.py:2257 #, python-brace-format msgid "" "Cannot setup apt pin. Empty apt repo origin value for {entitlement_name}" msgstr "" -#: ../../uaclient/messages/__init__.py:2232 +#: ../../uaclient/messages/__init__.py:2266 #, python-brace-format msgid "Could not determine contract delta service type {orig} {new}" msgstr "" -#: ../../uaclient/messages/__init__.py:2236 +#: ../../uaclient/messages/__init__.py:2272 +#, python-brace-format +msgid "" +"Cannot enable {service_being_enabled} when {required_service} is disabled.\n" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2280 #, python-brace-format msgid "" -"Error on Pro Image:\n" +"Cannot enable {service_being_enabled} when {incompatible_service} is enabled." +msgstr "" + +#: ../../uaclient/messages/__init__.py:2288 +#, python-brace-format +msgid "" +"Cannot disable {service_being_disabled} when {dependent_service} is " +"enabled.\n" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2297 +#, python-brace-format +msgid "" +"Failed to identify this image as a valid Ubuntu Pro image.\n" +"Details:\n" "{error_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2242 +#: ../../uaclient/messages/__init__.py:2307 #, python-brace-format msgid "" "An error occurred while talking the the cloud metadata service: {code} - " "{body}" msgstr "" -#: ../../uaclient/messages/__init__.py:2249 +#: ../../uaclient/messages/__init__.py:2314 #, python-brace-format msgid "" "Failed to attach machine\n" @@ -3170,16 +3261,16 @@ "For more information, see {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2259 +#: ../../uaclient/messages/__init__.py:2324 #, python-brace-format msgid "No valid AWS IMDS endpoint discovered at addresses: {addresses}" msgstr "" -#: ../../uaclient/messages/__init__.py:2266 +#: ../../uaclient/messages/__init__.py:2331 msgid "Unable to determine cloud platform." msgstr "" -#: ../../uaclient/messages/__init__.py:2274 +#: ../../uaclient/messages/__init__.py:2339 #, python-brace-format msgid "" "Auto-attach image support is not available on this image\n" @@ -3188,7 +3279,7 @@ "Suporte para auto-attach não está disponível para esta imagem\n" "Veja {url}" -#: ../../uaclient/messages/__init__.py:2283 +#: ../../uaclient/messages/__init__.py:2348 #, python-brace-format msgid "" "Auto-attach image support is not available on {{cloud_type}}\n" @@ -3197,18 +3288,18 @@ "Suporte para auto-attach não está disponível para {{cloud_type}}\n" "Veja: {url}" -#: ../../uaclient/messages/__init__.py:2291 +#: ../../uaclient/messages/__init__.py:2356 #, python-brace-format msgid "{file_name} is not valid {file_format}" msgstr "" -#: ../../uaclient/messages/__init__.py:2297 +#: ../../uaclient/messages/__init__.py:2362 #, python-brace-format msgid "" "Could not parse /etc/os-release VERSION: {orig_ver} (modified to {mod_ver})" msgstr "" -#: ../../uaclient/messages/__init__.py:2305 +#: ../../uaclient/messages/__init__.py:2370 #, python-brace-format msgid "" "Could not extract series information from /etc/os-release.\n" @@ -3219,7 +3310,7 @@ "o campo VERSION não tem a informação de versão: {version}\n" "e o campo VERSION_CODENAME não está presente" -#: ../../uaclient/messages/__init__.py:2315 +#: ../../uaclient/messages/__init__.py:2380 #, python-brace-format msgid "" "There is a corrupted lock file in the system. To continue, please remove it\n" @@ -3233,12 +3324,12 @@ "\n" "$ sudo rm {lock_file_path}" -#: ../../uaclient/messages/__init__.py:2324 +#: ../../uaclient/messages/__init__.py:2389 #, python-brace-format msgid "{source} returned invalid json: {out}" msgstr "{source} retornou um json inválido: {out}" -#: ../../uaclient/messages/__init__.py:2330 +#: ../../uaclient/messages/__init__.py:2395 #, python-brace-format msgid "" "Invalid value for {path_to_value} in /etc/ubuntu-advantage/uaclient.conf. " @@ -3247,7 +3338,7 @@ "Valor inválido para {path_to_value} em /etc/ubuntu-advantage/uaclient.conf." "Esperava {expected_value}, mas {value} foi encontrado." -#: ../../uaclient/messages/__init__.py:2339 +#: ../../uaclient/messages/__init__.py:2404 #, python-brace-format msgid "" "Cannot set {key} to {value}: for interval must be a positive integer." @@ -3255,17 +3346,17 @@ "Não foi possível associar {key} a {value}: precisa ser um inteiro " "positivo." -#: ../../uaclient/messages/__init__.py:2346 +#: ../../uaclient/messages/__init__.py:2411 #, python-brace-format msgid "Invalid url in config. {key}: {value}" msgstr "url inválida no arquivo de configuração. {key}: {value}" -#: ../../uaclient/messages/__init__.py:2351 +#: ../../uaclient/messages/__init__.py:2416 #, python-brace-format msgid "Could not find yaml file: {filepath}" msgstr "Não foi possível encontrar o arquivo yaml: {filepath}" -#: ../../uaclient/messages/__init__.py:2357 +#: ../../uaclient/messages/__init__.py:2422 msgid "" "Error: Setting global apt proxy and pro scoped apt proxy\n" "at the same time is unsupported.\n" @@ -3276,27 +3367,27 @@ "pro de apt ao mesmo tempo não é suportado.\n" "Cancelando o processo de configuração.\n" -#: ../../uaclient/messages/__init__.py:2367 +#: ../../uaclient/messages/__init__.py:2432 msgid "Can't load the distro-info database." msgstr "Não foi possível carregar o banco de dados do distro-info." -#: ../../uaclient/messages/__init__.py:2372 +#: ../../uaclient/messages/__init__.py:2437 #, python-brace-format msgid "Can't find series {series} in the distro-info database." msgstr "" "Não foi possível encontrar a série {series} na banco de dados do distro-info." -#: ../../uaclient/messages/__init__.py:2377 +#: ../../uaclient/messages/__init__.py:2442 #, python-brace-format msgid "Error: Cannot use {option1} together with {option2}." msgstr "Erro: não é possível usar {option1} junto com {option2}" -#: ../../uaclient/messages/__init__.py:2381 +#: ../../uaclient/messages/__init__.py:2446 #, python-brace-format msgid "No help available for '{name}'" msgstr "Ajuda não disponível para '{name}'" -#: ../../uaclient/messages/__init__.py:2387 +#: ../../uaclient/messages/__init__.py:2452 #, python-brace-format msgid "" "Error: issue \"{issue}\" is not recognized.\n" @@ -3305,34 +3396,39 @@ "Erro: problema de segurnça \"{issue}\" não foi reconhecido.\n" "Use: \"pro fix CVE-yyyy-nnnn\" ou \"pro fix USN-nnnn\"" -#: ../../uaclient/messages/__init__.py:2393 +#: ../../uaclient/messages/__init__.py:2458 #, python-brace-format msgid "{arg} must be one of: {choices}" msgstr "{arg} precisa ser um de: {choices}" -#: ../../uaclient/messages/__init__.py:2398 +#: ../../uaclient/messages/__init__.py:2463 +#, python-brace-format +msgid "Empty value provided for {arg}." +msgstr "" + +#: ../../uaclient/messages/__init__.py:2468 #, python-brace-format msgid "Expected {expected} but found: {actual}" msgstr "Esperava {expected} mas encontrou: {actual}" -#: ../../uaclient/messages/__init__.py:2402 +#: ../../uaclient/messages/__init__.py:2472 msgid "Unable to process uaclient.conf" msgstr "Falha ao processar uaclient.conf" -#: ../../uaclient/messages/__init__.py:2407 +#: ../../uaclient/messages/__init__.py:2477 msgid "Unable to refresh your subscription" msgstr "Falha ao atualizar sua assinatura" -#: ../../uaclient/messages/__init__.py:2412 +#: ../../uaclient/messages/__init__.py:2482 msgid "Unable to update Ubuntu Pro related APT and MOTD messages." msgstr "" "Falha ao atualizar as mensagens de APT e MOTD relacioandas ao Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:2418 +#: ../../uaclient/messages/__init__.py:2488 msgid "json formatted response requires --assume-yes flag." msgstr "resposta formatada em json necessita do paramêtro --assume-yes" -#: ../../uaclient/messages/__init__.py:2426 +#: ../../uaclient/messages/__init__.py:2496 msgid "" "Do not pass the TOKEN arg if you are using --attach-config.\n" "Include the token in the attach-config file instead.\n" @@ -3342,50 +3438,54 @@ "Ao invés disso, inclua o token no arquivo de attach-config.\n" " " -#: ../../uaclient/messages/__init__.py:2435 +#: ../../uaclient/messages/__init__.py:2505 msgid "Cannot provide both --args and --data at the same time" msgstr "Não é possível usar --args e --data ao mesmo tempo" -#: ../../uaclient/messages/__init__.py:2441 +#: ../../uaclient/messages/__init__.py:2510 +msgid "Operation cancelled by user" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2516 #, python-brace-format msgid "Unable to perform: {lock_request}.\n" msgstr "Falha ao executar: {lock_request}.\n" -#: ../../uaclient/messages/__init__.py:2450 +#: ../../uaclient/messages/__init__.py:2525 msgid "This command must be run as root (try using sudo)." msgstr "Esse comando precisa ser executado como root (tente usando sudo)." -#: ../../uaclient/messages/__init__.py:2455 +#: ../../uaclient/messages/__init__.py:2530 #, python-brace-format msgid "Metadata for {issue} is invalid. Error: {error_msg}." msgstr "Metadados para {issue} não são válidos. Erro: {error_msg}." -#: ../../uaclient/messages/__init__.py:2462 +#: ../../uaclient/messages/__init__.py:2537 #, python-brace-format msgid "Error: {issue_id} not found." msgstr "Erro: {issue_id} não encontrada." -#: ../../uaclient/messages/__init__.py:2466 +#: ../../uaclient/messages/__init__.py:2541 #, python-brace-format msgid "GPG key '{keyfile}' not found." msgstr "chave GPG '{keyfile}' não foi encontrada" -#: ../../uaclient/messages/__init__.py:2471 +#: ../../uaclient/messages/__init__.py:2546 #, python-brace-format msgid "'{endpoint}' is not a valid endpoint" msgstr "'{endpoint}' não é um endpoint válido" -#: ../../uaclient/messages/__init__.py:2476 +#: ../../uaclient/messages/__init__.py:2551 #, python-brace-format msgid "Missing argument '{arg}' for endpoint {endpoint}" msgstr "'{arg}' está faltando para endpoint {endpoint}" -#: ../../uaclient/messages/__init__.py:2481 +#: ../../uaclient/messages/__init__.py:2556 #, python-brace-format msgid "{endpoint} accepts no arguments" msgstr "{endpoint} não aceita paramêtros" -#: ../../uaclient/messages/__init__.py:2486 +#: ../../uaclient/messages/__init__.py:2561 #, python-brace-format msgid "" "Error parsing API json data parameter:\n" @@ -3394,32 +3494,32 @@ "Error ao analisar paramêtro data para API json:\n" "{data}" -#: ../../uaclient/messages/__init__.py:2491 +#: ../../uaclient/messages/__init__.py:2566 #, python-brace-format msgid "'{arg}' is not formatted as 'key=value'" msgstr "'{arg}' não está formatado como 'chave=valor'" -#: ../../uaclient/messages/__init__.py:2496 +#: ../../uaclient/messages/__init__.py:2571 #, python-brace-format msgid "Unable to determine version: {error_msg}" msgstr "Não foi possível determinar a versão: {error_msg}" -#: ../../uaclient/messages/__init__.py:2501 +#: ../../uaclient/messages/__init__.py:2576 msgid "features.disable_auto_attach set in config" msgstr "features.disable_auto_attach definida na configuração" -#: ../../uaclient/messages/__init__.py:2506 +#: ../../uaclient/messages/__init__.py:2581 #, python-brace-format msgid "Unable to determine unattended-upgrades status: {error_msg}" msgstr "" "Não foi possível determinar o status do unattended-upgrades: {error_msg}" -#: ../../uaclient/messages/__init__.py:2512 +#: ../../uaclient/messages/__init__.py:2587 #, python-brace-format msgid "Expected value with type {expected_type} but got type: {got_type}" msgstr "Esperava valor com tipo {expected_type}, mas recebeu tipo {got_type}" -#: ../../uaclient/messages/__init__.py:2518 +#: ../../uaclient/messages/__init__.py:2593 #, python-brace-format msgid "" "Got value with incorrect type at index {index}:\n" @@ -3428,7 +3528,7 @@ "Valor com tipo incorreto na posição {index}:\n" "{nested_msg}" -#: ../../uaclient/messages/__init__.py:2524 +#: ../../uaclient/messages/__init__.py:2599 #, python-brace-format msgid "" "Got value with incorrect type for field \"{key}\":\n" @@ -3437,13 +3537,41 @@ "Valor com tipo incorreto para o campo \"{key}\":\n" "{nested_msg}" -#: ../../uaclient/messages/__init__.py:2531 +#: ../../uaclient/messages/__init__.py:2606 #, python-brace-format msgid "Value provided was not found in {enum_class}'s allowed: value: {values}" msgstr "" "Valor fornecido não está presente nos valores permitidos de {enum_class}: " "{values}" +#: ../../uaclient/messages/__init__.py:2617 +#, python-brace-format +msgid "Error updating ESM services cache: {error}" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2623 +#, python-brace-format +msgid "" +"There is a problem with the resource directives provided by {url}\n" +"These entitlements: {names} are sharing the following directives\n" +" - APT url: {apt_url}\n" +" - Suite: {suite}\n" +"These directives need to be unique for every entitlement." +msgstr "" + +#: ../../uaclient/messages/__init__.py:2632 +msgid "landscape-config command failed" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2638 +msgid "" +"You must use the pro command to purge a service that has installed a kernel" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2645 +msgid "The operation is not supported" +msgstr "" + #~ msgid "Detach this machine from Ubuntu Pro services." #~ msgstr "Desvincule esta máquina de uma assinatura Ubuntu Pro" diff -Nru ubuntu-advantage-tools-31.2.3~16.04/debian/po/ubuntu-pro.pot ubuntu-advantage-tools-32~16.04/debian/po/ubuntu-pro.pot --- ubuntu-advantage-tools-31.2.3~16.04/debian/po/ubuntu-pro.pot 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/debian/po/ubuntu-pro.pot 2024-05-10 17:07:05.000000000 +0000 @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-19 16:13-0500\n" +"POT-Creation-Date: 2024-04-26 14:29-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,186 +18,186 @@ "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -#: ../../apt-hook/json-hook.cc:159 +#: ../../apt-hook/json-hook.cc:245 msgid "1 standard LTS security update" msgstr "" -#: ../../apt-hook/json-hook.cc:164 +#: ../../apt-hook/json-hook.cc:250 #, c-format msgid "%lu standard LTS security updates" msgstr "" -#: ../../apt-hook/json-hook.cc:171 +#: ../../apt-hook/json-hook.cc:257 msgid "1 esm-infra security update" msgstr "" -#: ../../apt-hook/json-hook.cc:173 +#: ../../apt-hook/json-hook.cc:259 msgid "1 standard LTS security update and 1 esm-infra security update" msgstr "" -#: ../../apt-hook/json-hook.cc:178 +#: ../../apt-hook/json-hook.cc:264 #, c-format msgid "%lu standard LTS security updates and 1 esm-infra security update" msgstr "" -#: ../../apt-hook/json-hook.cc:188 +#: ../../apt-hook/json-hook.cc:274 #, c-format msgid "%lu esm-infra security updates" msgstr "" -#: ../../apt-hook/json-hook.cc:196 +#: ../../apt-hook/json-hook.cc:282 #, c-format msgid "1 standard LTS security update and %lu esm-infra security updates" msgstr "" -#: ../../apt-hook/json-hook.cc:204 +#: ../../apt-hook/json-hook.cc:290 #, c-format msgid "%lu standard LTS security updates and %lu esm-infra security updates" msgstr "" -#: ../../apt-hook/json-hook.cc:214 +#: ../../apt-hook/json-hook.cc:300 msgid "1 esm-apps security update" msgstr "" -#: ../../apt-hook/json-hook.cc:216 +#: ../../apt-hook/json-hook.cc:302 msgid "1 standard LTS security update and 1 esm-apps security update" msgstr "" -#: ../../apt-hook/json-hook.cc:221 +#: ../../apt-hook/json-hook.cc:307 #, c-format msgid "%lu standard LTS security updates and 1 esm-apps security update" msgstr "" -#: ../../apt-hook/json-hook.cc:228 +#: ../../apt-hook/json-hook.cc:314 msgid "1 esm-infra security update and 1 esm-apps security update" msgstr "" -#: ../../apt-hook/json-hook.cc:230 +#: ../../apt-hook/json-hook.cc:316 msgid "" "1 standard LTS security update, 1 esm-infra security update and 1 esm-apps " "security update" msgstr "" -#: ../../apt-hook/json-hook.cc:235 +#: ../../apt-hook/json-hook.cc:321 #, c-format msgid "" "%lu standard LTS security updates, 1 esm-infra security update and 1 esm-" "apps security update" msgstr "" -#: ../../apt-hook/json-hook.cc:245 +#: ../../apt-hook/json-hook.cc:331 #, c-format msgid "%lu esm-infra security updates and 1 esm-apps security update" msgstr "" -#: ../../apt-hook/json-hook.cc:253 +#: ../../apt-hook/json-hook.cc:339 #, c-format msgid "" "1 standard LTS security update, %lu esm-infra security updates and 1 esm-" "apps security update" msgstr "" -#: ../../apt-hook/json-hook.cc:261 +#: ../../apt-hook/json-hook.cc:347 #, c-format msgid "" "%lu standard LTS security updates, %lu esm-infra security updates and 1 esm-" "apps security update" msgstr "" -#: ../../apt-hook/json-hook.cc:274 +#: ../../apt-hook/json-hook.cc:360 #, c-format msgid "%lu esm-apps security updates" msgstr "" -#: ../../apt-hook/json-hook.cc:282 +#: ../../apt-hook/json-hook.cc:368 #, c-format msgid "1 standard LTS security update and %lu esm-apps security updates" msgstr "" -#: ../../apt-hook/json-hook.cc:290 +#: ../../apt-hook/json-hook.cc:376 #, c-format msgid "%lu standard LTS security updates and %lu esm-apps security updates" msgstr "" -#: ../../apt-hook/json-hook.cc:301 +#: ../../apt-hook/json-hook.cc:387 #, c-format msgid "1 esm-infra security update and %lu esm-apps security updates" msgstr "" -#: ../../apt-hook/json-hook.cc:309 +#: ../../apt-hook/json-hook.cc:395 #, c-format msgid "" "1 standard LTS security update, 1 esm-infra security update and %lu esm-apps " "security updates" msgstr "" -#: ../../apt-hook/json-hook.cc:317 +#: ../../apt-hook/json-hook.cc:403 #, c-format msgid "" "%lu standard LTS security updates, 1 esm-infra security update and %lu esm-" "apps security updates" msgstr "" -#: ../../apt-hook/json-hook.cc:328 +#: ../../apt-hook/json-hook.cc:414 #, c-format msgid "%lu esm-infra security updates and %lu esm-apps security updates" msgstr "" -#: ../../apt-hook/json-hook.cc:337 +#: ../../apt-hook/json-hook.cc:423 #, c-format msgid "" "1 standard LTS security update, %lu esm-infra security updates and %lu esm-" "apps security updates" msgstr "" -#: ../../apt-hook/json-hook.cc:346 +#: ../../apt-hook/json-hook.cc:432 #, c-format msgid "" "%lu standard LTS security updates, %lu esm-infra security updates and %lu " "esm-apps security updates" msgstr "" -#: ../../apt-hook/json-hook.cc:403 +#: ../../apt-hook/json-hook.cc:489 #, c-format msgid "Learn more about Ubuntu Pro for 16.04 on Azure at %s" msgstr "" -#: ../../apt-hook/json-hook.cc:412 +#: ../../apt-hook/json-hook.cc:498 #, c-format msgid "Learn more about Ubuntu Pro for 16.04 at %s" msgstr "" -#: ../../apt-hook/json-hook.cc:423 +#: ../../apt-hook/json-hook.cc:509 #, c-format msgid "Learn more about Ubuntu Pro for 18.04 on Azure at %s" msgstr "" -#: ../../apt-hook/json-hook.cc:432 +#: ../../apt-hook/json-hook.cc:518 #, c-format msgid "Learn more about Ubuntu Pro for 18.04 at %s" msgstr "" -#: ../../apt-hook/json-hook.cc:443 +#: ../../apt-hook/json-hook.cc:529 #, c-format msgid "Learn more about Ubuntu Pro on Azure at %s" msgstr "" -#: ../../apt-hook/json-hook.cc:452 +#: ../../apt-hook/json-hook.cc:538 #, c-format msgid "Learn more about Ubuntu Pro on AWS at %s" msgstr "" -#: ../../apt-hook/json-hook.cc:461 +#: ../../apt-hook/json-hook.cc:547 #, c-format msgid "Learn more about Ubuntu Pro on GCP at %s" msgstr "" -#: ../../apt-hook/json-hook.cc:472 +#: ../../apt-hook/json-hook.cc:558 #, c-format msgid "Learn more about Ubuntu Pro at %s" msgstr "" -#: ../../apt-hook/json-hook.cc:485 +#: ../../apt-hook/json-hook.cc:584 #, c-format msgid "Get another security update through Ubuntu Pro with 'esm-apps' enabled:" msgid_plural "" @@ -205,7 +205,7 @@ msgstr[0] "" msgstr[1] "" -#: ../../apt-hook/json-hook.cc:494 +#: ../../apt-hook/json-hook.cc:593 #, c-format msgid "" "The following security update requires Ubuntu Pro with 'esm-infra' enabled:" @@ -214,6 +214,18 @@ msgstr[0] "" msgstr[1] "" +#: ../../apt-hook/json-hook.cc:609 +#, c-format +msgid "" +"The following packages will fail to download because your Ubuntu Pro " +"subscription has expired" +msgstr "" + +#: ../../apt-hook/json-hook.cc:618 +#, c-format +msgid "Renew your subscription or `sudo pro detach` to remove these errors" +msgstr "" + #. Description #: ../ubuntu-advantage-tools.templates:3 msgid "Ubuntu Pro support now requires ubuntu-advantage-pro" @@ -316,108 +328,112 @@ "to get the latest bug fixes and new features." msgstr "" +#: ../../uaclient/messages/__init__.py:112 +msgid "an unknown error" +msgstr "" + #. ############################################################################## #. GENERIC SYSTEM OPERATIONS # #. ############################################################################## -#: ../../uaclient/messages/__init__.py:118 +#: ../../uaclient/messages/__init__.py:119 #, python-brace-format msgid "Executing `{command}`" msgstr "" -#: ../../uaclient/messages/__init__.py:119 +#: ../../uaclient/messages/__init__.py:120 #, python-brace-format msgid "Executing `{command}` failed." msgstr "" -#: ../../uaclient/messages/__init__.py:120 +#: ../../uaclient/messages/__init__.py:121 #, python-brace-format msgid "Invalid command specified '{cmd}'." msgstr "" -#: ../../uaclient/messages/__init__.py:122 +#: ../../uaclient/messages/__init__.py:123 #, python-brace-format msgid "Failed running command '{cmd}' [exit({exit_code})]. Message: {stderr}" msgstr "" -#: ../../uaclient/messages/__init__.py:125 +#: ../../uaclient/messages/__init__.py:126 #, python-brace-format msgid "Installing {packages}" msgstr "" -#: ../../uaclient/messages/__init__.py:126 +#: ../../uaclient/messages/__init__.py:127 #, python-brace-format msgid "Installing {title} packages" msgstr "" -#: ../../uaclient/messages/__init__.py:127 +#: ../../uaclient/messages/__init__.py:128 msgid "Installing required snaps" msgstr "" -#: ../../uaclient/messages/__init__.py:129 +#: ../../uaclient/messages/__init__.py:130 #, python-brace-format msgid "Installing required snap: {snap}" msgstr "" -#: ../../uaclient/messages/__init__.py:132 +#: ../../uaclient/messages/__init__.py:133 #, python-brace-format msgid "Skipping installing packages: {packages}" msgstr "" -#: ../../uaclient/messages/__init__.py:134 +#: ../../uaclient/messages/__init__.py:135 #, python-brace-format msgid "Uninstalling {packages}" msgstr "" -#: ../../uaclient/messages/__init__.py:136 +#: ../../uaclient/messages/__init__.py:137 #, python-brace-format msgid "Failure when uninstalling {packages}" msgstr "" -#: ../../uaclient/messages/__init__.py:139 +#: ../../uaclient/messages/__init__.py:140 #, python-brace-format msgid "Cannot install package {package} version {version}" msgstr "" -#: ../../uaclient/messages/__init__.py:142 +#: ../../uaclient/messages/__init__.py:143 msgid "Failure checking APT policy." msgstr "" -#: ../../uaclient/messages/__init__.py:143 +#: ../../uaclient/messages/__init__.py:144 msgid "Updating package lists" msgstr "" -#: ../../uaclient/messages/__init__.py:144 +#: ../../uaclient/messages/__init__.py:145 #, python-brace-format msgid "Updating {name} package lists" msgstr "" -#: ../../uaclient/messages/__init__.py:145 +#: ../../uaclient/messages/__init__.py:146 msgid "APT update failed." msgstr "" -#: ../../uaclient/messages/__init__.py:146 +#: ../../uaclient/messages/__init__.py:147 msgid "APT install failed." msgstr "" -#: ../../uaclient/messages/__init__.py:148 +#: ../../uaclient/messages/__init__.py:149 #, python-brace-format msgid "Backing up {original} as {backup}" msgstr "" -#: ../../uaclient/messages/__init__.py:149 +#: ../../uaclient/messages/__init__.py:150 msgid "The following package(s) will be REMOVED:" msgstr "" -#: ../../uaclient/messages/__init__.py:151 +#: ../../uaclient/messages/__init__.py:152 msgid "The following package(s) will be reinstalled from the archive:" msgstr "" -#: ../../uaclient/messages/__init__.py:162 +#: ../../uaclient/messages/__init__.py:163 #, python-brace-format msgid "" "*Your Ubuntu Pro subscription has EXPIRED*\n" -"{{pkg_num}} additional security update require Ubuntu Pro with '{{service}}' " -"enabled.\n" +"{{pkg_num}} additional security update requires Ubuntu Pro with " +"'{{service}}' enabled.\n" "Renew your subscription at {url}" msgid_plural "" "*Your Ubuntu Pro subscription has EXPIRED*\n" @@ -427,7 +443,7 @@ msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:175 +#: ../../uaclient/messages/__init__.py:176 #, python-brace-format msgid "" "CAUTION: Your Ubuntu Pro subscription will expire in {{remaining_days}} " @@ -442,7 +458,7 @@ msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:188 +#: ../../uaclient/messages/__init__.py:189 #, python-brace-format msgid "" "CAUTION: Your Ubuntu Pro subscription expired on {{expired_date}}.\n" @@ -457,7 +473,7 @@ msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:202 +#: ../../uaclient/messages/__init__.py:203 #, python-brace-format msgid "" "*Your Ubuntu Pro subscription has EXPIRED*\n" @@ -467,36 +483,36 @@ #. ############################################################################## #. CONFIGURATION # #. ############################################################################## -#: ../../uaclient/messages/__init__.py:213 +#: ../../uaclient/messages/__init__.py:214 #, python-brace-format msgid "Setting {service} proxy" msgstr "" -#: ../../uaclient/messages/__init__.py:215 +#: ../../uaclient/messages/__init__.py:216 #, python-brace-format msgid "" "No proxy set in config; however, proxy is configured for: {{services}}.\n" "See {url} for more information on pro proxy configuration.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:220 +#: ../../uaclient/messages/__init__.py:221 #, python-brace-format msgid "Setting {scope} APT proxy" msgstr "" -#: ../../uaclient/messages/__init__.py:222 +#: ../../uaclient/messages/__init__.py:223 msgid "" "\n" "Error: Setting global apt proxy and pro scoped apt proxy at the same time is " "unsupported. No apt proxy is set." msgstr "" -#: ../../uaclient/messages/__init__.py:226 +#: ../../uaclient/messages/__init__.py:227 #, python-brace-format msgid "Warning: {old} has been renamed to {new}." msgstr "" -#: ../../uaclient/messages/__init__.py:230 +#: ../../uaclient/messages/__init__.py:231 #, python-brace-format msgid "" "Warning: Setting the {current_proxy} proxy will overwrite the " @@ -504,71 +520,71 @@ "proxy previously set via `pro config`.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:236 +#: ../../uaclient/messages/__init__.py:237 #, python-brace-format msgid "" "Using deprecated \"{old}\" config field.\n" "Please migrate to using \"{new}\"\n" msgstr "" -#: ../../uaclient/messages/__init__.py:243 +#: ../../uaclient/messages/__init__.py:244 msgid "Migrating /etc/ubuntu-advantage/uaclient.conf" msgstr "" -#: ../../uaclient/messages/__init__.py:246 +#: ../../uaclient/messages/__init__.py:247 msgid "" "Warning: Failed to load /etc/ubuntu-advantage/uaclient.conf.preinst-backup\n" " No automatic migration will occur.\n" " You may need to use \"pro config set\" to re-set your settings." msgstr "" -#: ../../uaclient/messages/__init__.py:253 +#: ../../uaclient/messages/__init__.py:254 msgid "" "Warning: Failed to migrate user_config from /etc/ubuntu-advantage/uaclient." "conf\n" " Please run the following to keep your custom settings:" msgstr "" -#: ../../uaclient/messages/__init__.py:259 +#: ../../uaclient/messages/__init__.py:260 msgid "" "Warning: Failed to migrate /etc/ubuntu-advantage/uaclient.conf\n" " Please add following to uaclient.conf to keep your config:" msgstr "" -#: ../../uaclient/messages/__init__.py:272 +#: ../../uaclient/messages/__init__.py:273 msgid "" "Currently attempting to automatically attach this machine to an Ubuntu Pro " "subscription" msgstr "" -#: ../../uaclient/messages/__init__.py:276 +#: ../../uaclient/messages/__init__.py:277 #, python-brace-format msgid "This machine is now attached to '{contract_name}'\n" msgstr "" -#: ../../uaclient/messages/__init__.py:281 +#: ../../uaclient/messages/__init__.py:282 msgid "This machine is now successfully attached'\n" msgstr "" -#: ../../uaclient/messages/__init__.py:285 +#: ../../uaclient/messages/__init__.py:286 #, python-brace-format msgid "Enabling default service {name}" msgstr "" -#: ../../uaclient/messages/__init__.py:287 +#: ../../uaclient/messages/__init__.py:288 #, python-brace-format msgid "Service {name} is recommended by default. Run: sudo pro enable {name}" msgstr "" -#: ../../uaclient/messages/__init__.py:290 +#: ../../uaclient/messages/__init__.py:291 msgid "Initiating attach operation..." msgstr "" -#: ../../uaclient/messages/__init__.py:291 +#: ../../uaclient/messages/__init__.py:292 msgid "Failed to perform attach..." msgstr "" -#: ../../uaclient/messages/__init__.py:293 +#: ../../uaclient/messages/__init__.py:294 #, python-brace-format msgid "" "Please sign in to your Ubuntu Pro account at this link:\n" @@ -576,40 +592,45 @@ "And provide the following code: {bold}{{user_code}}{end_bold}" msgstr "" -#: ../../uaclient/messages/__init__.py:302 +#: ../../uaclient/messages/__init__.py:303 msgid "Attaching the machine..." msgstr "" -#: ../../uaclient/messages/__init__.py:307 +#: ../../uaclient/messages/__init__.py:308 msgid "Detach will disable the following service:" msgid_plural "Detach will disable the following services:" msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:312 +#: ../../uaclient/messages/__init__.py:313 msgid "This machine is now detached." msgstr "" -#: ../../uaclient/messages/__init__.py:316 +#: ../../uaclient/messages/__init__.py:317 msgid "One moment, checking your subscription first" msgstr "" -#: ../../uaclient/messages/__init__.py:318 +#: ../../uaclient/messages/__init__.py:319 +#, python-brace-format +msgid "Enabling {title}" +msgstr "" + +#: ../../uaclient/messages/__init__.py:320 #, python-brace-format msgid "{title} enabled" msgstr "" -#: ../../uaclient/messages/__init__.py:319 +#: ../../uaclient/messages/__init__.py:321 #, python-brace-format msgid "{title} access enabled" msgstr "" -#: ../../uaclient/messages/__init__.py:320 +#: ../../uaclient/messages/__init__.py:322 #, python-brace-format msgid "Could not enable {title}." msgstr "" -#: ../../uaclient/messages/__init__.py:322 +#: ../../uaclient/messages/__init__.py:324 #, python-brace-format msgid "" "{service_being_enabled} cannot be enabled with {incompatible_service}.\n" @@ -617,12 +638,12 @@ "{service_being_enabled}? (y/N) " msgstr "" -#: ../../uaclient/messages/__init__.py:328 +#: ../../uaclient/messages/__init__.py:330 #, python-brace-format msgid "Disabling incompatible service: {service}" msgstr "" -#: ../../uaclient/messages/__init__.py:331 +#: ../../uaclient/messages/__init__.py:333 #, python-brace-format msgid "" "{service_being_enabled} cannot be enabled with {required_service} disabled.\n" @@ -630,23 +651,33 @@ "N) " msgstr "" -#: ../../uaclient/messages/__init__.py:336 +#: ../../uaclient/messages/__init__.py:338 #, python-brace-format msgid "Enabling required service: {service}" msgstr "" -#: ../../uaclient/messages/__init__.py:338 +#: ../../uaclient/messages/__init__.py:340 #, python-brace-format msgid "A reboot is required to complete {operation}." msgstr "" -#. DISABLE #: ../../uaclient/messages/__init__.py:343 #, python-brace-format +msgid "Configuring APT access to {service}" +msgstr "" + +#. DISABLE +#: ../../uaclient/messages/__init__.py:346 +#, python-brace-format +msgid "Removing APT access to {title}" +msgstr "" + +#: ../../uaclient/messages/__init__.py:347 +#, python-brace-format msgid "Could not disable {title}." msgstr "" -#: ../../uaclient/messages/__init__.py:345 +#: ../../uaclient/messages/__init__.py:349 #, python-brace-format msgid "" "{dependent_service} depends on {service_being_disabled}.\n" @@ -654,50 +685,55 @@ "(y/N) " msgstr "" -#: ../../uaclient/messages/__init__.py:351 +#: ../../uaclient/messages/__init__.py:355 #, python-brace-format msgid "Disabling dependent service: {required_service}" msgstr "" -#: ../../uaclient/messages/__init__.py:354 +#: ../../uaclient/messages/__init__.py:358 #, python-brace-format msgid "Removing apt source file: {filename}" msgstr "" -#: ../../uaclient/messages/__init__.py:356 +#: ../../uaclient/messages/__init__.py:360 #, python-brace-format msgid "Removing apt preferences file: {filename}" msgstr "" -#: ../../uaclient/messages/__init__.py:361 +#: ../../uaclient/messages/__init__.py:363 +#, python-brace-format +msgid "Uninstalling all packages installed from {title}" +msgstr "" + +#: ../../uaclient/messages/__init__.py:368 msgid "(The --purge flag is still experimental - use with caution)" msgstr "" -#: ../../uaclient/messages/__init__.py:364 +#: ../../uaclient/messages/__init__.py:371 #, python-brace-format msgid "Purging the {service} packages would uninstall the following kernel(s):" msgstr "" -#: ../../uaclient/messages/__init__.py:367 +#: ../../uaclient/messages/__init__.py:374 #, python-brace-format msgid "{kernel_version} is the current running kernel." msgstr "" -#: ../../uaclient/messages/__init__.py:370 +#: ../../uaclient/messages/__init__.py:377 msgid "" "No other valid Ubuntu kernel was found in the system.\n" "Removing the package would potentially make the system unbootable.\n" "Aborting.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:377 +#: ../../uaclient/messages/__init__.py:384 msgid "" "If you cannot guarantee that other kernels in this system are bootable and\n" "working properly, *do not proceed*. You may end up with an unbootable " "system.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:386 +#: ../../uaclient/messages/__init__.py:393 #, python-brace-format msgid "" "Failed to automatically attach to an Ubuntu Pro subscription {num_attempts} " @@ -707,7 +743,7 @@ "You can try manually with `sudo pro auto-attach`." msgstr "" -#: ../../uaclient/messages/__init__.py:394 +#: ../../uaclient/messages/__init__.py:401 #, python-brace-format msgid "" "Failed to automatically attach to an Ubuntu Pro subscription {num_attempts} " @@ -718,187 +754,183 @@ "You can try manually with `sudo pro auto-attach`." msgstr "" -#: ../../uaclient/messages/__init__.py:402 +#: ../../uaclient/messages/__init__.py:409 #, python-brace-format msgid "" "Canonical servers did not recognize this machine as Ubuntu Pro: \"{detail}\"" msgstr "" -#: ../../uaclient/messages/__init__.py:406 +#: ../../uaclient/messages/__init__.py:413 msgid "Canonical servers did not recognize this image as Ubuntu Pro" msgstr "" -#: ../../uaclient/messages/__init__.py:408 +#: ../../uaclient/messages/__init__.py:415 #, python-brace-format msgid "the pro lock was held by pid {pid}" msgstr "" -#: ../../uaclient/messages/__init__.py:410 +#: ../../uaclient/messages/__init__.py:417 #, python-brace-format msgid "an error from Canonical servers: \"{error_msg}\"" msgstr "" -#: ../../uaclient/messages/__init__.py:412 +#: ../../uaclient/messages/__init__.py:419 msgid "a connectivity error" msgstr "" -#: ../../uaclient/messages/__init__.py:413 +#: ../../uaclient/messages/__init__.py:420 #, python-brace-format msgid "an error while reaching {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:414 -msgid "an unknown error" -msgstr "" - -#: ../../uaclient/messages/__init__.py:418 +#: ../../uaclient/messages/__init__.py:424 #, python-brace-format msgid "Due to contract refresh, '{service}' is now disabled." msgstr "" -#: ../../uaclient/messages/__init__.py:421 +#: ../../uaclient/messages/__init__.py:427 #, python-brace-format msgid "" "Unable to disable '{service}' as recommended during contract refresh. " "Service is still active. See `pro status`" msgstr "" -#: ../../uaclient/messages/__init__.py:426 +#: ../../uaclient/messages/__init__.py:432 #, python-brace-format msgid "Updating '{service}' on changed directives." msgstr "" -#: ../../uaclient/messages/__init__.py:429 +#: ../../uaclient/messages/__init__.py:435 #, python-brace-format msgid "Updating '{service}' apt sources list on changed directives." msgstr "" -#: ../../uaclient/messages/__init__.py:432 +#: ../../uaclient/messages/__init__.py:438 #, python-brace-format msgid "Installing packages on changed directives: {packages}" msgstr "" -#: ../../uaclient/messages/__init__.py:442 +#: ../../uaclient/messages/__init__.py:448 #, python-brace-format msgid "Choose: [S]ubscribe at {url} [A]ttach existing token [C]ancel" msgstr "" -#: ../../uaclient/messages/__init__.py:446 +#: ../../uaclient/messages/__init__.py:452 #, python-brace-format msgid "Choose: [E]nable {service} [C]ancel" msgstr "" -#: ../../uaclient/messages/__init__.py:450 +#: ../../uaclient/messages/__init__.py:456 #, python-brace-format msgid "Choose: [R]enew your subscription (at {url}) [C]ancel" msgstr "" -#: ../../uaclient/messages/__init__.py:453 +#: ../../uaclient/messages/__init__.py:459 #, python-brace-format msgid "A fix is available in {fix_stream}." msgstr "" -#: ../../uaclient/messages/__init__.py:454 +#: ../../uaclient/messages/__init__.py:460 msgid "The update is not yet installed." msgstr "" -#: ../../uaclient/messages/__init__.py:456 +#: ../../uaclient/messages/__init__.py:462 msgid "" "The update is not installed because this system is not attached to a\n" "subscription.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:462 +#: ../../uaclient/messages/__init__.py:468 msgid "" "The update is not installed because this system is attached to an\n" "expired subscription.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:468 +#: ../../uaclient/messages/__init__.py:474 #, python-brace-format msgid "" "The update is not installed because this system does not have\n" "{service} enabled.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:473 +#: ../../uaclient/messages/__init__.py:479 msgid "The update is already installed." msgstr "" -#: ../../uaclient/messages/__init__.py:475 +#: ../../uaclient/messages/__init__.py:481 #, python-brace-format msgid "" "For easiest security on {title}, use Ubuntu Pro instances.\n" "Learn more at {cloud_specific_url}" msgstr "" -#: ../../uaclient/messages/__init__.py:480 +#: ../../uaclient/messages/__init__.py:486 msgid "requested" msgstr "" -#: ../../uaclient/messages/__init__.py:481 +#: ../../uaclient/messages/__init__.py:487 msgid "related" msgstr "" -#: ../../uaclient/messages/__init__.py:482 +#: ../../uaclient/messages/__init__.py:488 #, python-brace-format msgid " {issue} is resolved." msgstr "" -#: ../../uaclient/messages/__init__.py:484 +#: ../../uaclient/messages/__init__.py:490 #, python-brace-format msgid " {issue} [{context}] is resolved." msgstr "" -#: ../../uaclient/messages/__init__.py:486 +#: ../../uaclient/messages/__init__.py:492 #, python-brace-format msgid " {issue} is not resolved." msgstr "" -#: ../../uaclient/messages/__init__.py:488 +#: ../../uaclient/messages/__init__.py:494 #, python-brace-format msgid " {issue} [{context}] is not resolved." msgstr "" -#: ../../uaclient/messages/__init__.py:491 +#: ../../uaclient/messages/__init__.py:497 #, python-brace-format msgid " {issue} does not affect your system." msgstr "" -#: ../../uaclient/messages/__init__.py:494 +#: ../../uaclient/messages/__init__.py:500 #, python-brace-format msgid " {issue} [{context}] does not affect your system." msgstr "" -#: ../../uaclient/messages/__init__.py:498 +#: ../../uaclient/messages/__init__.py:504 #, python-brace-format msgid "{num_pkgs} package is still affected: {pkgs}" msgid_plural "{num_pkgs} packages are still affected: {pkgs}" msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:505 +#: ../../uaclient/messages/__init__.py:511 #, python-brace-format msgid "{count} affected source package is installed: {pkgs}" msgid_plural "{count} affected source packages are installed: {pkgs}" msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:511 +#: ../../uaclient/messages/__init__.py:517 msgid "No affected source packages are installed." msgstr "" -#: ../../uaclient/messages/__init__.py:513 +#: ../../uaclient/messages/__init__.py:519 #, python-brace-format msgid "{issue} is resolved." msgstr "" -#: ../../uaclient/messages/__init__.py:515 +#: ../../uaclient/messages/__init__.py:521 #, python-brace-format msgid " {issue} is resolved by livepatch patch version: {version}." msgstr "" -#: ../../uaclient/messages/__init__.py:518 +#: ../../uaclient/messages/__init__.py:524 #, python-brace-format msgid "" "{bold}Ubuntu Pro service: {{service}} is not enabled.\n" @@ -908,7 +940,7 @@ "{{{{ pro enable {{service}} }}}}{end_bold}" msgstr "" -#: ../../uaclient/messages/__init__.py:525 +#: ../../uaclient/messages/__init__.py:531 #, python-brace-format msgid "" "{bold}The machine is not attached to an Ubuntu Pro subscription.\n" @@ -916,7 +948,7 @@ "{{ pro attach TOKEN }}{end_bold}" msgstr "" -#: ../../uaclient/messages/__init__.py:531 +#: ../../uaclient/messages/__init__.py:537 #, python-brace-format msgid "" "{bold}The machine has an expired subscription.\n" @@ -926,104 +958,104 @@ "{{ pro attach NEW_TOKEN }}{end_bold}" msgstr "" -#: ../../uaclient/messages/__init__.py:539 +#: ../../uaclient/messages/__init__.py:545 #, python-brace-format msgid "" "{bold}WARNING: The option --dry-run is being used.\n" "No packages will be installed when running this command.{end_bold}" msgstr "" -#: ../../uaclient/messages/__init__.py:544 +#: ../../uaclient/messages/__init__.py:550 #, python-brace-format msgid "" "Error: Ubuntu Pro service: {service} is not enabled.\n" "Without it, we cannot fix the system." msgstr "" -#: ../../uaclient/messages/__init__.py:549 +#: ../../uaclient/messages/__init__.py:555 #, python-brace-format msgid "" "Error: The current Ubuntu Pro subscription is not entitled to: {service}.\n" "Without it, we cannot fix the system." msgstr "" -#: ../../uaclient/messages/__init__.py:554 +#: ../../uaclient/messages/__init__.py:560 #, python-brace-format msgid "{service} is required for upgrade." msgstr "" -#: ../../uaclient/messages/__init__.py:558 +#: ../../uaclient/messages/__init__.py:564 #, python-brace-format msgid "{service} is required for upgrade, but current subscription is expired." msgstr "" -#: ../../uaclient/messages/__init__.py:562 +#: ../../uaclient/messages/__init__.py:568 #, python-brace-format msgid "{service} is required for upgrade, but it is not enabled." msgstr "" -#: ../../uaclient/messages/__init__.py:566 +#: ../../uaclient/messages/__init__.py:572 msgid "APT failed to install the package.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:571 +#: ../../uaclient/messages/__init__.py:577 msgid "Sorry, no fix is available yet." msgstr "" -#: ../../uaclient/messages/__init__.py:575 +#: ../../uaclient/messages/__init__.py:581 msgid "Ubuntu security engineers are investigating this issue." msgstr "" -#: ../../uaclient/messages/__init__.py:579 +#: ../../uaclient/messages/__init__.py:585 msgid "A fix is coming soon. Try again tomorrow." msgstr "" -#: ../../uaclient/messages/__init__.py:583 +#: ../../uaclient/messages/__init__.py:589 msgid "Sorry, no fix is available." msgstr "" -#: ../../uaclient/messages/__init__.py:587 +#: ../../uaclient/messages/__init__.py:593 msgid "Source package does not exist on this release." msgstr "" -#: ../../uaclient/messages/__init__.py:591 +#: ../../uaclient/messages/__init__.py:597 msgid "Source package is not affected on this release." msgstr "" -#: ../../uaclient/messages/__init__.py:595 +#: ../../uaclient/messages/__init__.py:601 #, python-brace-format msgid "UNKNOWN: {status}" msgstr "" -#: ../../uaclient/messages/__init__.py:599 +#: ../../uaclient/messages/__init__.py:605 msgid "Associated CVEs:" msgstr "" -#: ../../uaclient/messages/__init__.py:600 +#: ../../uaclient/messages/__init__.py:606 msgid "Found Launchpad bugs:" msgstr "" -#: ../../uaclient/messages/__init__.py:602 +#: ../../uaclient/messages/__init__.py:608 #, python-brace-format msgid "Fixing requested {issue_id}" msgstr "" -#: ../../uaclient/messages/__init__.py:606 +#: ../../uaclient/messages/__init__.py:612 msgid "Fixing related USNs:" msgstr "" -#: ../../uaclient/messages/__init__.py:610 +#: ../../uaclient/messages/__init__.py:616 #, python-brace-format msgid "" "Found related USNs:\n" "- {related_usns}" msgstr "" -#: ../../uaclient/messages/__init__.py:614 +#: ../../uaclient/messages/__init__.py:620 msgid "Summary:" msgstr "" -#: ../../uaclient/messages/__init__.py:618 +#: ../../uaclient/messages/__init__.py:624 #, python-brace-format msgid "" "Even though a related USN failed to be fixed, note\n" @@ -1034,149 +1066,149 @@ "{url}\n" msgstr "" -#: ../../uaclient/messages/__init__.py:627 +#: ../../uaclient/messages/__init__.py:633 msgid "Ubuntu standard updates" msgstr "" -#: ../../uaclient/messages/__init__.py:628 -#: ../../uaclient/messages/__init__.py:1222 +#: ../../uaclient/messages/__init__.py:634 +#: ../../uaclient/messages/__init__.py:1242 msgid "Ubuntu Pro: ESM Infra" msgstr "" -#: ../../uaclient/messages/__init__.py:629 -#: ../../uaclient/messages/__init__.py:1208 +#: ../../uaclient/messages/__init__.py:635 +#: ../../uaclient/messages/__init__.py:1228 msgid "Ubuntu Pro: ESM Apps" msgstr "" -#: ../../uaclient/messages/__init__.py:632 +#: ../../uaclient/messages/__init__.py:638 msgid "" "Package fixes cannot be installed.\n" "To install them, run this command as root (try using sudo)" msgstr "" -#: ../../uaclient/messages/__init__.py:638 +#: ../../uaclient/messages/__init__.py:644 #, python-brace-format msgid "Enter your token (from {url}) to attach this system:" msgstr "" -#: ../../uaclient/messages/__init__.py:642 +#: ../../uaclient/messages/__init__.py:648 msgid "Enter your new token to renew Ubuntu Pro subscription on this system:" msgstr "" #. ############################################################################## #. SECURITYSTATUS SUBCOMMAND # #. ############################################################################## -#: ../../uaclient/messages/__init__.py:652 +#: ../../uaclient/messages/__init__.py:658 #, python-brace-format msgid "{count} packages installed:" msgstr "" -#: ../../uaclient/messages/__init__.py:655 +#: ../../uaclient/messages/__init__.py:661 #, python-brace-format msgid "{offset}{count} package from Ubuntu {repository} repository" msgid_plural "{offset}{count} packages from Ubuntu {repository} repository" msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:662 +#: ../../uaclient/messages/__init__.py:668 #, python-brace-format msgid "{offset}{count} package from a third party" msgid_plural "{offset}{count} packages from third parties" msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:669 +#: ../../uaclient/messages/__init__.py:675 #, python-brace-format msgid "{offset}{count} package no longer available for download" msgid_plural "{offset}{count} packages no longer available for download" msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:676 +#: ../../uaclient/messages/__init__.py:682 msgid "" "To get more information about the packages, run\n" " pro security-status --help\n" "for a list of available options." msgstr "" -#: ../../uaclient/messages/__init__.py:683 +#: ../../uaclient/messages/__init__.py:689 msgid "" " Make sure to run\n" " sudo apt update\n" "to get the latest package information from apt." msgstr "" -#: ../../uaclient/messages/__init__.py:689 +#: ../../uaclient/messages/__init__.py:695 #, python-brace-format msgid "The system apt information was updated {days} day(s) ago." msgstr "" -#: ../../uaclient/messages/__init__.py:693 +#: ../../uaclient/messages/__init__.py:699 msgid "The system apt cache may be outdated." msgstr "" -#: ../../uaclient/messages/__init__.py:697 +#: ../../uaclient/messages/__init__.py:703 #, python-brace-format msgid "Main/Restricted packages receive updates until {date}." msgstr "" -#: ../../uaclient/messages/__init__.py:700 +#: ../../uaclient/messages/__init__.py:706 #, python-brace-format msgid "" "This machine is receiving security patching for Ubuntu Main/Restricted\n" "repository until {date}." msgstr "" -#: ../../uaclient/messages/__init__.py:706 +#: ../../uaclient/messages/__init__.py:712 msgid "This machine is attached to an Ubuntu Pro subscription." msgstr "" -#: ../../uaclient/messages/__init__.py:709 +#: ../../uaclient/messages/__init__.py:715 msgid "This machine is NOT attached to an Ubuntu Pro subscription." msgstr "" -#: ../../uaclient/messages/__init__.py:713 +#: ../../uaclient/messages/__init__.py:719 msgid "" "Packages from third parties are not provided by the official Ubuntu\n" "archive, for example packages from Personal Package Archives in Launchpad." msgstr "" -#: ../../uaclient/messages/__init__.py:718 +#: ../../uaclient/messages/__init__.py:724 msgid "" "Packages that are not available for download may be left over from a\n" "previous release of Ubuntu, may have been installed directly from a\n" ".deb file, or are from a source which has been disabled." msgstr "" -#: ../../uaclient/messages/__init__.py:725 +#: ../../uaclient/messages/__init__.py:731 msgid "" "This machine is NOT receiving security patches because the LTS period has " "ended\n" "and esm-infra is not enabled." msgstr "" -#: ../../uaclient/messages/__init__.py:731 +#: ../../uaclient/messages/__init__.py:737 #, python-brace-format msgid "" "Ubuntu Pro with '{service}' enabled provides security updates for\n" "{repository} packages until {year}." msgstr "" -#: ../../uaclient/messages/__init__.py:737 +#: ../../uaclient/messages/__init__.py:743 #, python-brace-format msgid "There is {updates} pending security update." msgid_plural "There are {updates} pending security updates." msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:744 +#: ../../uaclient/messages/__init__.py:750 #, python-brace-format msgid "" "{repository} packages are receiving security updates from\n" "Ubuntu Pro with '{service}' enabled until {year}." msgstr "" -#: ../../uaclient/messages/__init__.py:750 +#: ../../uaclient/messages/__init__.py:756 #, python-brace-format msgid "" "You have received {updates} security\n" @@ -1187,19 +1219,19 @@ msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:760 +#: ../../uaclient/messages/__init__.py:766 #, python-brace-format msgid "Enable {service} with: pro enable {service}" msgstr "" -#: ../../uaclient/messages/__init__.py:762 +#: ../../uaclient/messages/__init__.py:768 #, python-brace-format msgid "" "Try Ubuntu Pro with a free personal subscription on up to 5 machines.\n" "Learn more at {url}\n" msgstr "" -#: ../../uaclient/messages/__init__.py:769 +#: ../../uaclient/messages/__init__.py:775 #, python-brace-format msgid "" "For example, run:\n" @@ -1207,161 +1239,161 @@ "to learn more about that package." msgstr "" -#: ../../uaclient/messages/__init__.py:776 +#: ../../uaclient/messages/__init__.py:782 msgid "You have no packages installed from a third party." msgstr "" -#: ../../uaclient/messages/__init__.py:779 +#: ../../uaclient/messages/__init__.py:785 msgid "You have no packages installed that are no longer available." msgstr "" -#: ../../uaclient/messages/__init__.py:782 +#: ../../uaclient/messages/__init__.py:788 msgid "Ubuntu Pro is not available for non-LTS releases." msgstr "" -#: ../../uaclient/messages/__init__.py:785 +#: ../../uaclient/messages/__init__.py:791 #, python-brace-format msgid "Run 'pro help {service}' to learn more" msgstr "" -#: ../../uaclient/messages/__init__.py:788 +#: ../../uaclient/messages/__init__.py:794 #, python-brace-format msgid "Installed packages with an available {service} update:" msgstr "" -#: ../../uaclient/messages/__init__.py:791 +#: ../../uaclient/messages/__init__.py:797 #, python-brace-format msgid "Installed packages with an {service} update applied:" msgstr "" -#: ../../uaclient/messages/__init__.py:793 +#: ../../uaclient/messages/__init__.py:799 #, python-brace-format msgid "Installed packages covered by {service}:" msgstr "" -#: ../../uaclient/messages/__init__.py:795 +#: ../../uaclient/messages/__init__.py:801 #, python-brace-format msgid "Further installed packages covered by {service}:" msgstr "" -#: ../../uaclient/messages/__init__.py:797 +#: ../../uaclient/messages/__init__.py:803 msgid "Packages:" msgstr "" #. ############################################################################## #. STATUS SUBCOMMAND # #. ############################################################################## -#: ../../uaclient/messages/__init__.py:805 +#: ../../uaclient/messages/__init__.py:811 msgid "SERVICE" msgstr "" -#: ../../uaclient/messages/__init__.py:806 +#: ../../uaclient/messages/__init__.py:812 msgid "AVAILABLE" msgstr "" -#: ../../uaclient/messages/__init__.py:807 +#: ../../uaclient/messages/__init__.py:813 msgid "ENTITLED" msgstr "" -#: ../../uaclient/messages/__init__.py:808 +#: ../../uaclient/messages/__init__.py:814 msgid "AUTO_ENABLED" msgstr "" -#: ../../uaclient/messages/__init__.py:809 +#: ../../uaclient/messages/__init__.py:815 msgid "STATUS" msgstr "" -#: ../../uaclient/messages/__init__.py:810 +#: ../../uaclient/messages/__init__.py:816 msgid "DESCRIPTION" msgstr "" -#: ../../uaclient/messages/__init__.py:811 +#: ../../uaclient/messages/__init__.py:817 msgid "NOTICES" msgstr "" -#: ../../uaclient/messages/__init__.py:812 +#: ../../uaclient/messages/__init__.py:818 msgid "FEATURES" msgstr "" -#: ../../uaclient/messages/__init__.py:816 +#: ../../uaclient/messages/__init__.py:822 msgid "enabled" msgstr "" -#: ../../uaclient/messages/__init__.py:817 +#: ../../uaclient/messages/__init__.py:823 msgid "disabled" msgstr "" -#: ../../uaclient/messages/__init__.py:818 +#: ../../uaclient/messages/__init__.py:824 msgid "n/a" msgstr "" -#: ../../uaclient/messages/__init__.py:820 +#: ../../uaclient/messages/__init__.py:826 msgid "warning" msgstr "" -#: ../../uaclient/messages/__init__.py:821 +#: ../../uaclient/messages/__init__.py:827 msgid "essential" msgstr "" -#: ../../uaclient/messages/__init__.py:822 +#: ../../uaclient/messages/__init__.py:828 msgid "standard" msgstr "" -#: ../../uaclient/messages/__init__.py:823 +#: ../../uaclient/messages/__init__.py:829 msgid "advanced" msgstr "" -#: ../../uaclient/messages/__init__.py:825 +#: ../../uaclient/messages/__init__.py:831 msgid "Unknown/Expired" msgstr "" -#: ../../uaclient/messages/__init__.py:828 +#: ../../uaclient/messages/__init__.py:834 #, python-brace-format msgid "Enable services with: {command}" msgstr "" -#: ../../uaclient/messages/__init__.py:830 +#: ../../uaclient/messages/__init__.py:836 msgid "Account" msgstr "" -#: ../../uaclient/messages/__init__.py:831 +#: ../../uaclient/messages/__init__.py:837 msgid "Subscription" msgstr "" -#: ../../uaclient/messages/__init__.py:832 +#: ../../uaclient/messages/__init__.py:838 msgid "Valid until" msgstr "" -#: ../../uaclient/messages/__init__.py:833 +#: ../../uaclient/messages/__init__.py:839 msgid "Technical support level" msgstr "" -#: ../../uaclient/messages/__init__.py:835 +#: ../../uaclient/messages/__init__.py:841 msgid "This token is not valid." msgstr "" -#: ../../uaclient/messages/__init__.py:836 +#: ../../uaclient/messages/__init__.py:842 msgid "No Ubuntu Pro operations are running" msgstr "" -#: ../../uaclient/messages/__init__.py:839 +#: ../../uaclient/messages/__init__.py:845 msgid "No Ubuntu Pro services are available to this system." msgstr "" -#: ../../uaclient/messages/__init__.py:842 +#: ../../uaclient/messages/__init__.py:848 msgid "For a list of all Ubuntu Pro services, run 'pro status --all'" msgstr "" -#: ../../uaclient/messages/__init__.py:844 +#: ../../uaclient/messages/__init__.py:850 msgid " * Service has variants" msgstr "" -#: ../../uaclient/messages/__init__.py:846 +#: ../../uaclient/messages/__init__.py:852 msgid "" "For a list of all Ubuntu Pro services and variants, run 'pro status --all'" msgstr "" -#: ../../uaclient/messages/__init__.py:851 +#: ../../uaclient/messages/__init__.py:857 msgid "" "A change has been detected in your contract.\n" "Please run `sudo pro refresh`." @@ -1372,107 +1404,113 @@ #. ############################################################################## #. This encompasses help text for subcommands, flags, and arguments for the CLI #. Also, any generic strings about the CLI itself go here. -#: ../../uaclient/messages/__init__.py:864 +#: ../../uaclient/messages/__init__.py:870 msgid "Try 'pro --help' for more information." msgstr "" -#: ../../uaclient/messages/__init__.py:866 +#: ../../uaclient/messages/__init__.py:872 #, python-brace-format msgid "Use {name} {command} --help for more information about a command." msgstr "" -#: ../../uaclient/messages/__init__.py:869 +#: ../../uaclient/messages/__init__.py:875 msgid "Use pro help to get more details about each service" msgstr "" -#: ../../uaclient/messages/__init__.py:872 +#: ../../uaclient/messages/__init__.py:878 msgid "Variants:" msgstr "" -#: ../../uaclient/messages/__init__.py:873 +#: ../../uaclient/messages/__init__.py:879 msgid "Arguments" msgstr "" -#: ../../uaclient/messages/__init__.py:874 +#: ../../uaclient/messages/__init__.py:880 msgid "Flags" msgstr "" -#: ../../uaclient/messages/__init__.py:875 +#: ../../uaclient/messages/__init__.py:881 msgid "Available Commands" msgstr "" -#: ../../uaclient/messages/__init__.py:877 +#: ../../uaclient/messages/__init__.py:883 #, python-brace-format msgid "output in the specified format (default: {default})" msgstr "" -#: ../../uaclient/messages/__init__.py:880 +#: ../../uaclient/messages/__init__.py:886 #, python-brace-format msgid "do not prompt for confirmation before performing the {command}" msgstr "" -#: ../../uaclient/messages/__init__.py:883 -#: ../../uaclient/messages/__init__.py:1103 +#: ../../uaclient/messages/__init__.py:889 +#: ../../uaclient/messages/__init__.py:1123 msgid "Calls the Client API endpoints." msgstr "" -#: ../../uaclient/messages/__init__.py:884 +#: ../../uaclient/messages/__init__.py:890 msgid "API endpoint to call" msgstr "" -#: ../../uaclient/messages/__init__.py:886 +#: ../../uaclient/messages/__init__.py:892 +msgid "" +"For endpoints that support progress updates, show each progress update on a " +"new line in JSON format" +msgstr "" + +#: ../../uaclient/messages/__init__.py:896 msgid "Options to pass to the API endpoint, formatted as key=value" msgstr "" -#: ../../uaclient/messages/__init__.py:888 +#: ../../uaclient/messages/__init__.py:898 msgid "arguments in JSON format to the API endpoint" msgstr "" -#: ../../uaclient/messages/__init__.py:891 +#: ../../uaclient/messages/__init__.py:901 msgid "Automatically attach on an Ubuntu Pro cloud instance." msgstr "" -#: ../../uaclient/messages/__init__.py:895 +#: ../../uaclient/messages/__init__.py:905 msgid "Collect logs and relevant system information into a tarball." msgstr "" -#: ../../uaclient/messages/__init__.py:898 -msgid "tarball where the logs will be stored. (Defaults to ./ua_logs.tar.gz)" +#: ../../uaclient/messages/__init__.py:908 +msgid "tarball where the logs will be stored. (Defaults to ./pro_logs.tar.gz)" msgstr "" -#: ../../uaclient/messages/__init__.py:901 +#: ../../uaclient/messages/__init__.py:911 msgid "Show customizable configuration settings" msgstr "" -#: ../../uaclient/messages/__init__.py:903 +#: ../../uaclient/messages/__init__.py:913 msgid "Optional key or key(s) to show configuration settings." msgstr "" -#: ../../uaclient/messages/__init__.py:906 +#: ../../uaclient/messages/__init__.py:916 msgid "Set and apply Ubuntu Pro configuration settings" msgstr "" -#: ../../uaclient/messages/__init__.py:909 +#: ../../uaclient/messages/__init__.py:919 #, python-brace-format msgid "" "key=value pair to configure for Ubuntu Pro services. Key must be one of: " "{options}" msgstr "" -#: ../../uaclient/messages/__init__.py:912 +#: ../../uaclient/messages/__init__.py:922 msgid "Unset Ubuntu Pro configuration setting" msgstr "" -#: ../../uaclient/messages/__init__.py:914 +#: ../../uaclient/messages/__init__.py:924 #, python-brace-format msgid "configuration key to unset from Ubuntu Pro services. One of: {options}" msgstr "" -#: ../../uaclient/messages/__init__.py:916 +#: ../../uaclient/messages/__init__.py:926 msgid "Manage Ubuntu Pro configuration" msgstr "" -#: ../../uaclient/messages/__init__.py:919 +#: ../../uaclient/messages/__init__.py:929 #, python-brace-format msgid "" "Attach this machine to an Ubuntu Pro subscription with a token obtained " @@ -1484,43 +1522,56 @@ "a web browser." msgstr "" -#: ../../uaclient/messages/__init__.py:927 +#: ../../uaclient/messages/__init__.py:937 msgid "token obtained for Ubuntu Pro authentication" msgstr "" -#: ../../uaclient/messages/__init__.py:929 +#: ../../uaclient/messages/__init__.py:939 msgid "do not enable any recommended services automatically" msgstr "" -#: ../../uaclient/messages/__init__.py:932 +#: ../../uaclient/messages/__init__.py:942 msgid "" "use the provided attach config file instead of passing the token on the cli" msgstr "" -#: ../../uaclient/messages/__init__.py:937 +#: ../../uaclient/messages/__init__.py:947 msgid "" "Inspect and resolve CVEs and USNs (Ubuntu Security Notices) on this machine." msgstr "" -#: ../../uaclient/messages/__init__.py:941 +#: ../../uaclient/messages/__init__.py:951 msgid "" "Security vulnerability ID to inspect and resolve on this system. Format: CVE-" "yyyy-nnnn, CVE-yyyy-nnnnnnn or USN-nnnn-dd" msgstr "" -#: ../../uaclient/messages/__init__.py:945 +#: ../../uaclient/messages/__init__.py:955 msgid "" "If used, fix will not actually run but will display everything that will " "happen on the machine during the command." msgstr "" -#: ../../uaclient/messages/__init__.py:950 +#: ../../uaclient/messages/__init__.py:960 msgid "" "If used, when fixing a USN, the command will not try to also fix related " "USNs to the target USN." msgstr "" -#: ../../uaclient/messages/__init__.py:955 +#: ../../uaclient/messages/__init__.py:965 +msgid "" +"WARNING: Failed to update ESM cache - package availability may be inaccurate" +msgstr "" + +#: ../../uaclient/messages/__init__.py:969 +#, python-brace-format +msgid "" +"{bold}WARNING: Unable to update ESM cache when running as non-root,\n" +"please run sudo apt update and try again if packages cannot be found." +"{end_bold}" +msgstr "" + +#: ../../uaclient/messages/__init__.py:975 msgid "" "Show security updates for packages in the system, including all\n" "available Expanded Security Maintenance (ESM) related content.\n" @@ -1540,23 +1591,23 @@ "complete status on Ubuntu Pro services, run 'pro status'.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:975 +#: ../../uaclient/messages/__init__.py:995 msgid "List and present information about third-party packages" msgstr "" -#: ../../uaclient/messages/__init__.py:978 +#: ../../uaclient/messages/__init__.py:998 msgid "List and present information about unavailable packages" msgstr "" -#: ../../uaclient/messages/__init__.py:981 +#: ../../uaclient/messages/__init__.py:1001 msgid "List and present information about esm-infra packages" msgstr "" -#: ../../uaclient/messages/__init__.py:984 +#: ../../uaclient/messages/__init__.py:1004 msgid "List and present information about esm-apps packages" msgstr "" -#: ../../uaclient/messages/__init__.py:988 +#: ../../uaclient/messages/__init__.py:1008 msgid "" "Refresh three distinct Ubuntu Pro related artifacts in the system:\n" "\n" @@ -1569,72 +1620,72 @@ "is specified, all targets are refreshed.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1000 +#: ../../uaclient/messages/__init__.py:1020 msgid "Target to refresh." msgstr "" -#: ../../uaclient/messages/__init__.py:1003 +#: ../../uaclient/messages/__init__.py:1023 msgid "Detach this machine from an Ubuntu Pro subscription." msgstr "" -#: ../../uaclient/messages/__init__.py:1007 +#: ../../uaclient/messages/__init__.py:1027 msgid "Provide detailed information about Ubuntu Pro services." msgstr "" -#: ../../uaclient/messages/__init__.py:1010 +#: ../../uaclient/messages/__init__.py:1030 #, python-brace-format msgid "a service to view help output for. One of: {options}" msgstr "" -#: ../../uaclient/messages/__init__.py:1012 +#: ../../uaclient/messages/__init__.py:1032 msgid "Include beta services" msgstr "" -#: ../../uaclient/messages/__init__.py:1014 +#: ../../uaclient/messages/__init__.py:1034 msgid "Enable an Ubuntu Pro service." msgstr "" -#: ../../uaclient/messages/__init__.py:1016 +#: ../../uaclient/messages/__init__.py:1036 #, python-brace-format msgid "the name(s) of the Ubuntu Pro services to enable. One of: {options}" msgstr "" -#: ../../uaclient/messages/__init__.py:1019 +#: ../../uaclient/messages/__init__.py:1039 msgid "" "do not auto-install packages. Valid for cc-eal, cis and realtime-kernel." msgstr "" -#: ../../uaclient/messages/__init__.py:1022 +#: ../../uaclient/messages/__init__.py:1042 msgid "allow beta service to be enabled" msgstr "" -#: ../../uaclient/messages/__init__.py:1024 +#: ../../uaclient/messages/__init__.py:1044 msgid "The name of the variant to use when enabling the service" msgstr "" -#: ../../uaclient/messages/__init__.py:1027 +#: ../../uaclient/messages/__init__.py:1047 msgid "Disable an Ubuntu Pro service." msgstr "" -#: ../../uaclient/messages/__init__.py:1029 +#: ../../uaclient/messages/__init__.py:1049 #, python-brace-format msgid "the name(s) of the Ubuntu Pro services to disable. One of: {options}" msgstr "" -#: ../../uaclient/messages/__init__.py:1032 +#: ../../uaclient/messages/__init__.py:1052 msgid "" "disable the service and remove/downgrade related packages (experimental)" msgstr "" -#: ../../uaclient/messages/__init__.py:1036 +#: ../../uaclient/messages/__init__.py:1056 msgid "Output system related information related to Pro services" msgstr "" -#: ../../uaclient/messages/__init__.py:1038 +#: ../../uaclient/messages/__init__.py:1058 msgid "does the system need to be rebooted" msgstr "" -#: ../../uaclient/messages/__init__.py:1040 +#: ../../uaclient/messages/__init__.py:1060 msgid "" "Report the current reboot-required status for the machine.\n" "\n" @@ -1650,7 +1701,7 @@ " nearest maintenance window.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1057 +#: ../../uaclient/messages/__init__.py:1077 msgid "" "Report current status of Ubuntu Pro services on system.\n" "\n" @@ -1686,80 +1737,80 @@ "listed in the output.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1092 +#: ../../uaclient/messages/__init__.py:1112 msgid "Block waiting on pro to complete" msgstr "" -#: ../../uaclient/messages/__init__.py:1094 +#: ../../uaclient/messages/__init__.py:1114 msgid "simulate the output status using a provided token" msgstr "" -#: ../../uaclient/messages/__init__.py:1096 +#: ../../uaclient/messages/__init__.py:1116 msgid "Include unavailable and beta services" msgstr "" -#: ../../uaclient/messages/__init__.py:1098 +#: ../../uaclient/messages/__init__.py:1118 msgid "show all debug log messages to console" msgstr "" -#: ../../uaclient/messages/__init__.py:1099 +#: ../../uaclient/messages/__init__.py:1119 #, python-brace-format msgid "show version of {name}" msgstr "" -#: ../../uaclient/messages/__init__.py:1101 +#: ../../uaclient/messages/__init__.py:1121 msgid "attach this machine to an Ubuntu Pro subscription" msgstr "" -#: ../../uaclient/messages/__init__.py:1104 +#: ../../uaclient/messages/__init__.py:1124 msgid "automatically attach on supported platforms" msgstr "" -#: ../../uaclient/messages/__init__.py:1105 +#: ../../uaclient/messages/__init__.py:1125 msgid "collect Pro logs and debug information" msgstr "" -#: ../../uaclient/messages/__init__.py:1106 +#: ../../uaclient/messages/__init__.py:1126 msgid "manage Ubuntu Pro configuration on this machine" msgstr "" -#: ../../uaclient/messages/__init__.py:1108 +#: ../../uaclient/messages/__init__.py:1128 msgid "remove this machine from an Ubuntu Pro subscription" msgstr "" -#: ../../uaclient/messages/__init__.py:1111 +#: ../../uaclient/messages/__init__.py:1131 msgid "disable a specific Ubuntu Pro service on this machine" msgstr "" -#: ../../uaclient/messages/__init__.py:1114 +#: ../../uaclient/messages/__init__.py:1134 msgid "enable a specific Ubuntu Pro service on this machine" msgstr "" -#: ../../uaclient/messages/__init__.py:1117 +#: ../../uaclient/messages/__init__.py:1137 msgid "check for and mitigate the impact of a CVE/USN on this system" msgstr "" -#: ../../uaclient/messages/__init__.py:1120 +#: ../../uaclient/messages/__init__.py:1140 msgid "list available security updates for the system" msgstr "" -#: ../../uaclient/messages/__init__.py:1123 +#: ../../uaclient/messages/__init__.py:1143 msgid "show detailed information about Ubuntu Pro services" msgstr "" -#: ../../uaclient/messages/__init__.py:1125 +#: ../../uaclient/messages/__init__.py:1145 msgid "refresh Ubuntu Pro services" msgstr "" -#: ../../uaclient/messages/__init__.py:1126 +#: ../../uaclient/messages/__init__.py:1146 msgid "current status of all Ubuntu Pro services" msgstr "" -#: ../../uaclient/messages/__init__.py:1127 +#: ../../uaclient/messages/__init__.py:1147 msgid "show system information related to Pro services" msgstr "" -#: ../../uaclient/messages/__init__.py:1130 +#: ../../uaclient/messages/__init__.py:1150 #, python-brace-format msgid "" "WARNING: this output is intended to be human readable, and subject to " @@ -1771,15 +1822,15 @@ #. ############################################################################## #. SERVICE-SPECIFIC MESSAGES # #. ############################################################################## -#: ../../uaclient/messages/__init__.py:1143 +#: ../../uaclient/messages/__init__.py:1163 msgid "Anbox Cloud" msgstr "" -#: ../../uaclient/messages/__init__.py:1144 +#: ../../uaclient/messages/__init__.py:1164 msgid "Scalable Android in the cloud" msgstr "" -#: ../../uaclient/messages/__init__.py:1146 +#: ../../uaclient/messages/__init__.py:1166 #, python-brace-format msgid "" "Anbox Cloud lets you stream mobile apps securely, at any scale, to any " @@ -1797,7 +1848,7 @@ "{url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1157 +#: ../../uaclient/messages/__init__.py:1177 #, python-brace-format msgid "" "To finish setting up the Anbox Cloud Appliance, run:\n" @@ -1809,15 +1860,15 @@ "For more information, see {url}\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1168 +#: ../../uaclient/messages/__init__.py:1188 msgid "CC EAL2" msgstr "" -#: ../../uaclient/messages/__init__.py:1169 +#: ../../uaclient/messages/__init__.py:1189 msgid "Common Criteria EAL2 Provisioning Packages" msgstr "" -#: ../../uaclient/messages/__init__.py:1171 +#: ../../uaclient/messages/__init__.py:1191 msgid "" "Common Criteria is an Information Technology Security Evaluation standard\n" "(ISO/IEC IS 15408) for computer security certification. Ubuntu 16.04 has " @@ -1827,29 +1878,29 @@ "on Intel x86_64, IBM Power8 and IBM Z hardware platforms." msgstr "" -#: ../../uaclient/messages/__init__.py:1178 +#: ../../uaclient/messages/__init__.py:1198 msgid "" "(This will download more than 500MB of packages, so may take some time.)" msgstr "" -#: ../../uaclient/messages/__init__.py:1182 +#: ../../uaclient/messages/__init__.py:1202 #, python-brace-format msgid "Please follow instructions in {filename} to configure EAL2" msgstr "" -#: ../../uaclient/messages/__init__.py:1185 +#: ../../uaclient/messages/__init__.py:1205 msgid "CIS Audit" msgstr "" -#: ../../uaclient/messages/__init__.py:1186 +#: ../../uaclient/messages/__init__.py:1206 msgid "Ubuntu Security Guide" msgstr "" -#: ../../uaclient/messages/__init__.py:1187 +#: ../../uaclient/messages/__init__.py:1207 msgid "Security compliance and audit tools" msgstr "" -#: ../../uaclient/messages/__init__.py:1189 +#: ../../uaclient/messages/__init__.py:1209 #, python-brace-format msgid "" "Ubuntu Security Guide is a tool for hardening and auditing and allows for\n" @@ -1859,17 +1910,17 @@ "{url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1195 +#: ../../uaclient/messages/__init__.py:1215 #, python-brace-format msgid "Visit {url} to learn how to use CIS" msgstr "" -#: ../../uaclient/messages/__init__.py:1198 +#: ../../uaclient/messages/__init__.py:1218 #, python-brace-format msgid "Visit {url} for the next steps" msgstr "" -#: ../../uaclient/messages/__init__.py:1202 +#: ../../uaclient/messages/__init__.py:1222 #, python-brace-format msgid "" "From Ubuntu 20.04 onward 'pro enable cis' has been\n" @@ -1877,11 +1928,11 @@ "{url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1210 +#: ../../uaclient/messages/__init__.py:1230 msgid "Expanded Security Maintenance for Applications" msgstr "" -#: ../../uaclient/messages/__init__.py:1213 +#: ../../uaclient/messages/__init__.py:1233 #, python-brace-format msgid "" "Expanded Security Maintenance for Applications is enabled by default on\n" @@ -1893,11 +1944,11 @@ "{url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1224 +#: ../../uaclient/messages/__init__.py:1244 msgid "Expanded Security Maintenance for Infrastructure" msgstr "" -#: ../../uaclient/messages/__init__.py:1227 +#: ../../uaclient/messages/__init__.py:1247 #, python-brace-format msgid "" "Expanded Security Maintenance for Infrastructure provides access to a " @@ -1910,15 +1961,15 @@ "{url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1236 +#: ../../uaclient/messages/__init__.py:1256 msgid "FIPS" msgstr "" -#: ../../uaclient/messages/__init__.py:1237 +#: ../../uaclient/messages/__init__.py:1257 msgid "NIST-certified FIPS crypto packages" msgstr "" -#: ../../uaclient/messages/__init__.py:1239 +#: ../../uaclient/messages/__init__.py:1259 #, python-brace-format msgid "" "Installs FIPS 140 crypto packages for FedRAMP, FISMA and compliance use " @@ -1929,18 +1980,18 @@ "choose \"fips-updates\" for maximum security. Find out more at {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1246 +#: ../../uaclient/messages/__init__.py:1266 msgid "Could not determine cloud, defaulting to generic FIPS package." msgstr "" -#: ../../uaclient/messages/__init__.py:1249 +#: ../../uaclient/messages/__init__.py:1269 #, python-brace-format msgid "" "FIPS kernel is running in a disabled state.\n" " To manually remove fips kernel: {url}\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1255 +#: ../../uaclient/messages/__init__.py:1275 msgid "" "Warning: FIPS kernel is not optimized for your specific cloud.\n" "To fix it, run the following commands:\n" @@ -1951,20 +2002,20 @@ " 4. sudo reboot\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1267 +#: ../../uaclient/messages/__init__.py:1287 msgid "" "This will install the FIPS packages. The Livepatch service will be " "unavailable.\n" "Warning: This action can take some time and cannot be undone.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1276 +#: ../../uaclient/messages/__init__.py:1296 msgid "" "This will install the FIPS packages including security updates.\n" "Warning: This action can take some time and cannot be undone.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1285 +#: ../../uaclient/messages/__init__.py:1305 #, python-brace-format msgid "" "Warning: Enabling {title} in a container.\n" @@ -1974,51 +2025,61 @@ "Warning: This action can take some time and cannot be undone.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1297 +#: ../../uaclient/messages/__init__.py:1317 #, python-brace-format msgid "" "This will disable the {title} entitlement but the {title} packages will " "remain installed.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1304 +#: ../../uaclient/messages/__init__.py:1324 +#, python-brace-format +msgid "" +"This will downgrade the kernel from {current_version} to {new_version}.\n" +"Warning: Downgrading the kernel may cause hardware failures. Please ensure " +"the\n" +" hardware is compatible with the new kernel version before " +"proceeding.\n" +msgstr "" + +#: ../../uaclient/messages/__init__.py:1331 msgid "FIPS support requires system reboot to complete configuration." msgstr "" -#: ../../uaclient/messages/__init__.py:1306 -#: ../../uaclient/messages/__init__.py:1755 +#: ../../uaclient/messages/__init__.py:1333 +#: ../../uaclient/messages/__init__.py:1766 msgid "Reboot to FIPS kernel required" msgstr "" -#: ../../uaclient/messages/__init__.py:1308 +#: ../../uaclient/messages/__init__.py:1335 msgid "This FIPS install is out of date, run: sudo pro enable fips" msgstr "" -#: ../../uaclient/messages/__init__.py:1311 +#: ../../uaclient/messages/__init__.py:1338 msgid "Disabling FIPS requires system reboot to complete operation." msgstr "" -#: ../../uaclient/messages/__init__.py:1314 +#: ../../uaclient/messages/__init__.py:1341 #, python-brace-format msgid "{service} {pkg} package could not be installed" msgstr "" -#: ../../uaclient/messages/__init__.py:1317 +#: ../../uaclient/messages/__init__.py:1344 msgid "" "Please run `apt upgrade` to ensure all FIPS packages are updated to the " "correct\n" "version.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1323 +#: ../../uaclient/messages/__init__.py:1350 msgid "FIPS Updates" msgstr "" -#: ../../uaclient/messages/__init__.py:1325 +#: ../../uaclient/messages/__init__.py:1352 msgid "FIPS compliant crypto packages with stable security updates" msgstr "" -#: ../../uaclient/messages/__init__.py:1328 +#: ../../uaclient/messages/__init__.py:1355 #, python-brace-format msgid "" "fips-updates installs FIPS 140 crypto packages including all security " @@ -2027,21 +2088,21 @@ "You can find out more at {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1334 +#: ../../uaclient/messages/__init__.py:1361 msgid "FIPS Preview" msgstr "" -#: ../../uaclient/messages/__init__.py:1336 +#: ../../uaclient/messages/__init__.py:1363 msgid "Preview of FIPS crypto packages undergoing certification with NIST" msgstr "" -#: ../../uaclient/messages/__init__.py:1339 +#: ../../uaclient/messages/__init__.py:1366 msgid "" "Installs FIPS crypto packages that are under certification with NIST,\n" "for FedRAMP, FISMA and compliance use cases." msgstr "" -#: ../../uaclient/messages/__init__.py:1344 +#: ../../uaclient/messages/__init__.py:1371 msgid "" "This will install crypto packages that have been submitted to NIST for " "review\n" @@ -2053,15 +2114,15 @@ "Warning: This action can take some time and cannot be undone.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1355 +#: ../../uaclient/messages/__init__.py:1382 msgid "Landscape" msgstr "" -#: ../../uaclient/messages/__init__.py:1357 +#: ../../uaclient/messages/__init__.py:1384 msgid "Management and administration tool for Ubuntu" msgstr "" -#: ../../uaclient/messages/__init__.py:1360 +#: ../../uaclient/messages/__init__.py:1387 #, python-brace-format msgid "" "Landscape Client can be installed on this machine and enrolled in " @@ -2074,22 +2135,22 @@ "more. Find out more about Landscape at {home_url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1373 +#: ../../uaclient/messages/__init__.py:1400 msgid "" "/etc/landscape/client.conf contains your landscape-client configuration.\n" "To re-enable Landscape with the same configuration, run:\n" " sudo pro enable landscape --assume-yes\n" msgstr "" -#: ../../uaclient/messages/__init__.py:1380 +#: ../../uaclient/messages/__init__.py:1407 msgid "Livepatch" msgstr "" -#: ../../uaclient/messages/__init__.py:1381 +#: ../../uaclient/messages/__init__.py:1408 msgid "Canonical Livepatch service" msgstr "" -#: ../../uaclient/messages/__init__.py:1383 +#: ../../uaclient/messages/__init__.py:1410 #, python-brace-format msgid "" "Livepatch provides selected high and critical kernel CVE fixes and other\n" @@ -2104,42 +2165,50 @@ "service at {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1392 +#: ../../uaclient/messages/__init__.py:1419 msgid "Current kernel is not supported" msgstr "" -#: ../../uaclient/messages/__init__.py:1395 +#: ../../uaclient/messages/__init__.py:1422 #, python-brace-format msgid "Supported livepatch kernels are listed here: {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1398 +#: ../../uaclient/messages/__init__.py:1425 #, python-brace-format msgid "Unable to configure livepatch: {error_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:1400 +#: ../../uaclient/messages/__init__.py:1427 msgid "Unable to enable Livepatch: " msgstr "" -#: ../../uaclient/messages/__init__.py:1402 +#: ../../uaclient/messages/__init__.py:1429 msgid "Disabling Livepatch prior to re-attach with new token" msgstr "" -#: ../../uaclient/messages/__init__.py:1405 +#: ../../uaclient/messages/__init__.py:1432 msgid "Livepatch support requires a system reboot across LTS upgrade." msgstr "" -#: ../../uaclient/messages/__init__.py:1408 -#: ../../uaclient/messages/__init__.py:1421 +#: ../../uaclient/messages/__init__.py:1434 +msgid "Installing Livepatch" +msgstr "" + +#: ../../uaclient/messages/__init__.py:1435 +msgid "Setting up Livepatch" +msgstr "" + +#: ../../uaclient/messages/__init__.py:1437 +#: ../../uaclient/messages/__init__.py:1450 msgid "Real-time kernel" msgstr "" -#: ../../uaclient/messages/__init__.py:1410 +#: ../../uaclient/messages/__init__.py:1439 msgid "Ubuntu kernel with PREEMPT_RT patches integrated" msgstr "" -#: ../../uaclient/messages/__init__.py:1413 +#: ../../uaclient/messages/__init__.py:1442 msgid "" "The Real-time kernel is an Ubuntu kernel with PREEMPT_RT patches integrated. " "It\n" @@ -2152,35 +2221,35 @@ "Livepatch." msgstr "" -#: ../../uaclient/messages/__init__.py:1423 +#: ../../uaclient/messages/__init__.py:1452 msgid "Generic version of the RT kernel (default)" msgstr "" -#: ../../uaclient/messages/__init__.py:1425 +#: ../../uaclient/messages/__init__.py:1454 msgid "Real-time NVIDIA Tegra Kernel" msgstr "" -#: ../../uaclient/messages/__init__.py:1427 +#: ../../uaclient/messages/__init__.py:1456 msgid "RT kernel optimized for NVIDIA Tegra platform" msgstr "" -#: ../../uaclient/messages/__init__.py:1429 +#: ../../uaclient/messages/__init__.py:1458 msgid "Raspberry Pi Real-time for Pi5/Pi4" msgstr "" -#: ../../uaclient/messages/__init__.py:1431 +#: ../../uaclient/messages/__init__.py:1460 msgid "24.04 Real-time kernel optimised for Raspberry Pi" msgstr "" -#: ../../uaclient/messages/__init__.py:1433 +#: ../../uaclient/messages/__init__.py:1462 msgid "Real-time Intel IOTG Kernel" msgstr "" -#: ../../uaclient/messages/__init__.py:1435 +#: ../../uaclient/messages/__init__.py:1464 msgid "RT kernel optimized for Intel IOTG platform" msgstr "" -#: ../../uaclient/messages/__init__.py:1438 +#: ../../uaclient/messages/__init__.py:1467 #, python-brace-format msgid "" "The Real-time kernel is an Ubuntu kernel with PREEMPT_RT patches " @@ -2193,7 +2262,7 @@ "Do you want to continue? [ default = Yes ]: (Y/n) " msgstr "" -#: ../../uaclient/messages/__init__.py:1449 +#: ../../uaclient/messages/__init__.py:1478 msgid "" "This will remove the boot order preference for the Real-time kernel and\n" "disable updates to the Real-time kernel.\n" @@ -2210,15 +2279,15 @@ "Are you sure? (y/N) " msgstr "" -#: ../../uaclient/messages/__init__.py:1465 +#: ../../uaclient/messages/__init__.py:1494 msgid "ROS ESM Security Updates" msgstr "" -#: ../../uaclient/messages/__init__.py:1466 +#: ../../uaclient/messages/__init__.py:1495 msgid "Security Updates for the Robot Operating System" msgstr "" -#: ../../uaclient/messages/__init__.py:1468 +#: ../../uaclient/messages/__init__.py:1497 #, python-brace-format msgid "" "ros provides access to a private PPA which includes security-related " @@ -2231,15 +2300,15 @@ "{url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1477 +#: ../../uaclient/messages/__init__.py:1506 msgid "ROS ESM All Updates" msgstr "" -#: ../../uaclient/messages/__init__.py:1479 +#: ../../uaclient/messages/__init__.py:1508 msgid "All Updates for the Robot Operating System" msgstr "" -#: ../../uaclient/messages/__init__.py:1482 +#: ../../uaclient/messages/__init__.py:1511 #, python-brace-format msgid "" "ros-updates provides access to a private PPA that includes non-security-" @@ -2252,14 +2321,15 @@ "{url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1553 +#: ../../uaclient/messages/__init__.py:1582 +#, python-brace-format msgid "" -"Unexpected error(s) occurred.\n" -"For more details, see the log: /var/log/ubuntu-advantage.log\n" -"To file a bug run: ubuntu-bug ubuntu-advantage-tools" +"An unexpected error occurred: {error_msg}\n" +"For more details, see the log: {log_path}\n" +"If you think this is a bug, please run: ubuntu-bug ubuntu-advantage-tools" msgstr "" -#: ../../uaclient/messages/__init__.py:1563 +#: ../../uaclient/messages/__init__.py:1592 #, python-brace-format msgid "" "Failed to access URL: {url}\n" @@ -2267,7 +2337,7 @@ "Please install \"ca-certificates\" and try again." msgstr "" -#: ../../uaclient/messages/__init__.py:1573 +#: ../../uaclient/messages/__init__.py:1602 #, python-brace-format msgid "" "Failed to access URL: {url}\n" @@ -2275,303 +2345,293 @@ "Please check your openssl configuration." msgstr "" -#: ../../uaclient/messages/__init__.py:1582 +#: ../../uaclient/messages/__init__.py:1611 #, python-brace-format msgid "Ignoring unknown argument '{arg}'" msgstr "" -#: ../../uaclient/messages/__init__.py:1588 +#: ../../uaclient/messages/__init__.py:1617 #, python-brace-format msgid "" "A new version of the client is available: {version}. Please upgrade to the " "latest version to get the new features and bug fixes." msgstr "" -#: ../../uaclient/messages/__init__.py:1595 +#: ../../uaclient/messages/__init__.py:1624 #, python-brace-format msgid "{title} does not support being enabled with --access-only" msgstr "" -#: ../../uaclient/messages/__init__.py:1600 +#: ../../uaclient/messages/__init__.py:1629 #, python-brace-format msgid "{title} does not support being disabled with --purge" msgstr "" -#: ../../uaclient/messages/__init__.py:1606 +#: ../../uaclient/messages/__init__.py:1635 #, python-brace-format msgid "Cannot disable dependent service: {required_service}{error}" msgstr "" -#: ../../uaclient/messages/__init__.py:1613 -#, python-brace-format -msgid "" -"Cannot disable {service_being_disabled} when {dependent_service} is " -"enabled.\n" -msgstr "" - -#: ../../uaclient/messages/__init__.py:1621 +#: ../../uaclient/messages/__init__.py:1642 #, python-brace-format msgid "Cannot disable {entitlement_name} with purge: no origin value defined" msgstr "" -#: ../../uaclient/messages/__init__.py:1628 +#: ../../uaclient/messages/__init__.py:1649 #, python-brace-format msgid "Cannot enable required service: {service}{error}" msgstr "" -#: ../../uaclient/messages/__init__.py:1633 -#, python-brace-format -msgid "" -"Cannot enable {service_being_enabled} when {required_service} is disabled.\n" -msgstr "" - -#: ../../uaclient/messages/__init__.py:1641 -#, python-brace-format -msgid "" -"Cannot enable {service_being_enabled} when {incompatible_service} is enabled." -msgstr "" - -#: ../../uaclient/messages/__init__.py:1649 +#: ../../uaclient/messages/__init__.py:1654 #, python-brace-format msgid "Cannot install {title} on a container." msgstr "" -#: ../../uaclient/messages/__init__.py:1652 +#: ../../uaclient/messages/__init__.py:1657 #, python-brace-format msgid "{title} is not configured" msgstr "" -#: ../../uaclient/messages/__init__.py:1657 +#: ../../uaclient/messages/__init__.py:1662 #, python-brace-format msgid "" "The {service} service is not enabled because the {package} package is\n" "not installed." msgstr "" -#: ../../uaclient/messages/__init__.py:1663 +#: ../../uaclient/messages/__init__.py:1668 #, python-brace-format msgid "{title} is active" msgstr "" -#: ../../uaclient/messages/__init__.py:1667 +#: ../../uaclient/messages/__init__.py:1672 #, python-brace-format msgid "{title} does not have an aptURL directive" msgstr "" -#: ../../uaclient/messages/__init__.py:1672 +#: ../../uaclient/messages/__init__.py:1676 +#, python-brace-format +msgid "{title} does not have a suites directive" +msgstr "" + +#: ../../uaclient/messages/__init__.py:1681 #, python-brace-format msgid "" -"{title} is not currently enabled\n" +"{title} is not currently enabled - nothing to do.\n" "See: sudo pro status" msgstr "" -#: ../../uaclient/messages/__init__.py:1679 +#: ../../uaclient/messages/__init__.py:1689 #, python-brace-format msgid "" "Disabling {title} with pro is not supported.\n" "See: sudo pro status" msgstr "" -#: ../../uaclient/messages/__init__.py:1686 +#: ../../uaclient/messages/__init__.py:1696 #, python-brace-format msgid "" -"{title} is already enabled.\n" +"{title} is already enabled - nothing to do.\n" "See: sudo pro status" msgstr "" -#: ../../uaclient/messages/__init__.py:1693 +#: ../../uaclient/messages/__init__.py:1704 #, python-brace-format msgid "" "This subscription is not entitled to {{title}}\n" "View your subscription at: {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1699 +#: ../../uaclient/messages/__init__.py:1710 #, python-brace-format msgid "{title} is not entitled" msgstr "" -#: ../../uaclient/messages/__init__.py:1705 +#: ../../uaclient/messages/__init__.py:1716 #, python-brace-format msgid "" "{title} is not available for kernel {kernel}.\n" "Minimum kernel version required: {min_kernel}." msgstr "" -#: ../../uaclient/messages/__init__.py:1713 +#: ../../uaclient/messages/__init__.py:1724 #, python-brace-format msgid "" "{title} is not available for kernel {kernel}.\n" "Supported flavors are: {supported_kernels}." msgstr "" -#: ../../uaclient/messages/__init__.py:1721 +#: ../../uaclient/messages/__init__.py:1732 #, python-brace-format msgid "{title} is not available for Ubuntu {series}." msgstr "" -#: ../../uaclient/messages/__init__.py:1728 +#: ../../uaclient/messages/__init__.py:1739 #, python-brace-format msgid "" "{title} is not available for platform {arch}.\n" "Supported platforms are: {supported_arches}." msgstr "" -#: ../../uaclient/messages/__init__.py:1736 +#: ../../uaclient/messages/__init__.py:1747 #, python-brace-format msgid "" "{title} is not available for CPU vendor {vendor}.\n" "Supported CPU vendors are: {supported_vendors}." msgstr "" -#: ../../uaclient/messages/__init__.py:1743 +#: ../../uaclient/messages/__init__.py:1754 msgid "no entitlement affordances checked" msgstr "" -#: ../../uaclient/messages/__init__.py:1749 +#: ../../uaclient/messages/__init__.py:1760 #, python-brace-format msgid "" "Ubuntu {{series}} does not provide {{cloud}} optimized FIPS kernel\n" "For help see: {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:1759 +#: ../../uaclient/messages/__init__.py:1770 #, python-brace-format msgid "Cannot enable {fips} when {fips_updates} is enabled." msgstr "" -#: ../../uaclient/messages/__init__.py:1762 +#: ../../uaclient/messages/__init__.py:1773 #, python-brace-format msgid "{file_name} is not set to 1" msgstr "" -#: ../../uaclient/messages/__init__.py:1766 +#: ../../uaclient/messages/__init__.py:1777 #, python-brace-format msgid "Cannot enable {fips} because {fips_updates} was once enabled." msgstr "" -#: ../../uaclient/messages/__init__.py:1771 +#: ../../uaclient/messages/__init__.py:1782 msgid "" "FIPS cannot be enabled if FIPS Updates has ever been enabled because FIPS " "Updates installs security patches that aren't officially certified." msgstr "" -#: ../../uaclient/messages/__init__.py:1779 +#: ../../uaclient/messages/__init__.py:1790 msgid "" "FIPS Updates cannot be enabled if FIPS is enabled. FIPS Updates installs " "security patches that aren't officially certified." msgstr "" -#: ../../uaclient/messages/__init__.py:1788 +#: ../../uaclient/messages/__init__.py:1799 msgid "" "Livepatch cannot be enabled while running the official FIPS certified " "kernel. If you would like a FIPS compliant kernel with additional bug fixes " "and security updates, you can use the FIPS Updates service with Livepatch." msgstr "" -#: ../../uaclient/messages/__init__.py:1796 +#: ../../uaclient/messages/__init__.py:1807 msgid "canonical-livepatch snap is not installed." msgstr "" -#: ../../uaclient/messages/__init__.py:1800 +#: ../../uaclient/messages/__init__.py:1811 msgid "Cannot enable Livepatch when FIPS is enabled." msgstr "" -#: ../../uaclient/messages/__init__.py:1805 +#: ../../uaclient/messages/__init__.py:1816 msgid "" "The running kernel has reached the end of its active livepatch window.\n" "Please upgrade the kernel with apt and reboot for continued livepatch " "support." msgstr "" -#: ../../uaclient/messages/__init__.py:1813 +#: ../../uaclient/messages/__init__.py:1824 #, python-brace-format msgid "" "The current kernel ({{version}}, {{arch}}) has reached the end of its " "livepatch support.\n" "Supported kernels are listed here: {url}\n" -"Either switch to a supported kernel or `pro disable livepatch` to dismiss " -"this warning." +"Either switch to a supported kernel or `sudo pro disable livepatch` to " +"dismiss this warning." msgstr "" -#: ../../uaclient/messages/__init__.py:1822 +#: ../../uaclient/messages/__init__.py:1833 #, python-brace-format msgid "" "The current kernel ({{version}}, {{arch}}) is not supported by livepatch.\n" "Supported kernels are listed here: {url}\n" -"Either switch to a supported kernel or `pro disable livepatch` to dismiss " -"this warning." +"Either switch to a supported kernel or `sudo pro disable livepatch` to " +"dismiss this warning." msgstr "" -#: ../../uaclient/messages/__init__.py:1832 +#: ../../uaclient/messages/__init__.py:1843 msgid "canonical-livepatch status didn't finish successfully" msgstr "" -#: ../../uaclient/messages/__init__.py:1838 +#: ../../uaclient/messages/__init__.py:1849 #, python-brace-format msgid "" "Error running canonical-livepatch status:\n" "{livepatch_error}" msgstr "" -#: ../../uaclient/messages/__init__.py:1847 +#: ../../uaclient/messages/__init__.py:1858 msgid "" "Realtime and FIPS require different kernels, so you cannot enable both at " "the same time." msgstr "" -#: ../../uaclient/messages/__init__.py:1854 +#: ../../uaclient/messages/__init__.py:1865 msgid "" "Realtime and FIPS Updates require different kernels, so you cannot enable " "both at the same time." msgstr "" -#: ../../uaclient/messages/__init__.py:1861 +#: ../../uaclient/messages/__init__.py:1872 msgid "Livepatch is not currently supported for the Real-time kernel." msgstr "" -#: ../../uaclient/messages/__init__.py:1866 +#: ../../uaclient/messages/__init__.py:1877 #, python-brace-format msgid "{service} cannot be enabled together with {variant}" msgstr "" -#: ../../uaclient/messages/__init__.py:1870 +#: ../../uaclient/messages/__init__.py:1881 msgid "Cannot install Real-time kernel on a container." msgstr "" -#: ../../uaclient/messages/__init__.py:1875 +#: ../../uaclient/messages/__init__.py:1886 +msgid "ROS packages assume ESM updates are enabled." +msgstr "" + +#: ../../uaclient/messages/__init__.py:1891 +msgid "ROS bug-fix updates assume ROS security fix updates are enabled." +msgstr "" + +#: ../../uaclient/messages/__init__.py:1897 msgid "apt-daily.timer jobs are not running" msgstr "" -#: ../../uaclient/messages/__init__.py:1879 +#: ../../uaclient/messages/__init__.py:1901 #, python-brace-format msgid "{cfg_name} is empty" msgstr "" -#: ../../uaclient/messages/__init__.py:1883 +#: ../../uaclient/messages/__init__.py:1905 #, python-brace-format msgid "{cfg_name} is turned off" msgstr "" -#: ../../uaclient/messages/__init__.py:1887 +#: ../../uaclient/messages/__init__.py:1909 msgid "unattended-upgrades package is not installed" msgstr "" -#: ../../uaclient/messages/__init__.py:1893 +#: ../../uaclient/messages/__init__.py:1915 msgid "" "Landscape is installed and configured but not registered.\n" "Run `sudo landscape-config` to register, or run `sudo pro disable landscape`" msgstr "" -#: ../../uaclient/messages/__init__.py:1902 +#: ../../uaclient/messages/__init__.py:1924 msgid "landscape-client is either not installed or installed but disabled." msgstr "" -#: ../../uaclient/messages/__init__.py:1907 -msgid "landscape-config command failed" -msgstr "" - -#: ../../uaclient/messages/__init__.py:1913 +#: ../../uaclient/messages/__init__.py:1931 #, python-brace-format msgid "" "Error: issue \"{issue_id}\" is not recognized.\n" @@ -2581,28 +2641,28 @@ "USNs should follow the pattern USN-nnnn." msgstr "" -#: ../../uaclient/messages/__init__.py:1927 +#: ../../uaclient/messages/__init__.py:1950 msgid "Another process is running APT." msgstr "" -#: ../../uaclient/messages/__init__.py:1933 +#: ../../uaclient/messages/__init__.py:1956 #, python-brace-format msgid "" "APT update failed to read APT config for the following:\n" "{failed_repos}" msgstr "" -#: ../../uaclient/messages/__init__.py:1963 +#: ../../uaclient/messages/__init__.py:1986 #, python-brace-format msgid "Invalid APT credentials provided for {repo}" msgstr "" -#: ../../uaclient/messages/__init__.py:1968 +#: ../../uaclient/messages/__init__.py:1991 #, python-brace-format msgid "Timeout trying to access APT repository at {repo}" msgstr "" -#: ../../uaclient/messages/__init__.py:1974 +#: ../../uaclient/messages/__init__.py:1997 #, python-brace-format msgid "" "Unexpected APT error.\n" @@ -2610,107 +2670,107 @@ "See /var/log/ubuntu-advantage.log" msgstr "" -#: ../../uaclient/messages/__init__.py:1984 +#: ../../uaclient/messages/__init__.py:2007 #, python-brace-format msgid "" "Cannot validate credentials for APT repo. Timeout after {seconds} seconds " "trying to reach {repo}." msgstr "" -#: ../../uaclient/messages/__init__.py:1991 +#: ../../uaclient/messages/__init__.py:2014 #, python-brace-format msgid "snap {snap} is not installed or doesn't exist" msgstr "" -#: ../../uaclient/messages/__init__.py:1996 +#: ../../uaclient/messages/__init__.py:2019 #, python-brace-format msgid "" "Unexpected SNAPD API error\n" "{error}" msgstr "" -#: ../../uaclient/messages/__init__.py:2000 +#: ../../uaclient/messages/__init__.py:2023 msgid "Could not reach the SNAPD API" msgstr "" -#: ../../uaclient/messages/__init__.py:2004 +#: ../../uaclient/messages/__init__.py:2027 msgid "Failed to install snapd on the system" msgstr "" -#: ../../uaclient/messages/__init__.py:2009 +#: ../../uaclient/messages/__init__.py:2032 #, python-brace-format msgid "Unable to install Livepatch client: {error_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2014 +#: ../../uaclient/messages/__init__.py:2037 #, python-brace-format msgid "\"{proxy}\" is not working. Not setting as proxy." msgstr "" -#: ../../uaclient/messages/__init__.py:2019 +#: ../../uaclient/messages/__init__.py:2042 #, python-brace-format msgid "\"{proxy}\" is not a valid url. Not setting as proxy." msgstr "" -#: ../../uaclient/messages/__init__.py:2025 +#: ../../uaclient/messages/__init__.py:2048 msgid "" "To use an HTTPS proxy for HTTPS connections, please install pycurl with `apt " "install python3-pycurl`" msgstr "" -#: ../../uaclient/messages/__init__.py:2031 +#: ../../uaclient/messages/__init__.py:2054 #, python-brace-format msgid "PycURL Error: {e}" msgstr "" -#: ../../uaclient/messages/__init__.py:2035 +#: ../../uaclient/messages/__init__.py:2058 msgid "Proxy authentication failed" msgstr "" -#: ../../uaclient/messages/__init__.py:2041 +#: ../../uaclient/messages/__init__.py:2064 #, python-brace-format msgid "" "Failed to connect to {url}\n" "{cause_error}\n" msgstr "" -#: ../../uaclient/messages/__init__.py:2049 +#: ../../uaclient/messages/__init__.py:2072 #, python-brace-format msgid "Error connecting to {url}: {code} {body}" msgstr "" -#: ../../uaclient/messages/__init__.py:2055 +#: ../../uaclient/messages/__init__.py:2078 #, python-brace-format msgid "" "Cannot {operation} unknown service '{invalid_service}'.\n" "{service_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2064 +#: ../../uaclient/messages/__init__.py:2087 #, python-brace-format msgid "" "This machine is already attached to '{account_name}'\n" "To use a different subscription first run: sudo pro detach." msgstr "" -#: ../../uaclient/messages/__init__.py:2071 +#: ../../uaclient/messages/__init__.py:2094 #, python-brace-format msgid "Failed to attach machine. See {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2078 +#: ../../uaclient/messages/__init__.py:2101 #, python-brace-format msgid "" "Error while reading {config_name}:\n" "{error}" msgstr "" -#: ../../uaclient/messages/__init__.py:2083 +#: ../../uaclient/messages/__init__.py:2106 #, python-brace-format msgid "Invalid token. See {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2089 +#: ../../uaclient/messages/__init__.py:2112 #, python-brace-format msgid "" "Attach denied:\n" @@ -2718,7 +2778,7 @@ "Visit {url} to manage contract tokens." msgstr "" -#: ../../uaclient/messages/__init__.py:2099 +#: ../../uaclient/messages/__init__.py:2122 #, python-brace-format msgid "" "Attach denied:\n" @@ -2726,7 +2786,7 @@ "Visit {url} to manage contract tokens." msgstr "" -#: ../../uaclient/messages/__init__.py:2109 +#: ../../uaclient/messages/__init__.py:2132 #, python-brace-format msgid "" "Attach denied:\n" @@ -2734,101 +2794,132 @@ "Visit {url} to manage contract tokens." msgstr "" -#: ../../uaclient/messages/__init__.py:2119 +#: ../../uaclient/messages/__init__.py:2142 #, python-brace-format msgid "Expired token or contract. To obtain a new token visit: {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2126 +#: ../../uaclient/messages/__init__.py:2149 msgid "The magic attach token is already activated." msgstr "" -#: ../../uaclient/messages/__init__.py:2132 +#: ../../uaclient/messages/__init__.py:2155 msgid "The magic attach token is invalid, has expired or never existed" msgstr "" -#: ../../uaclient/messages/__init__.py:2138 +#: ../../uaclient/messages/__init__.py:2161 msgid "Service unavailable, please try again later." msgstr "" -#: ../../uaclient/messages/__init__.py:2143 +#: ../../uaclient/messages/__init__.py:2166 #, python-brace-format msgid "This attach flow does not support {param} with value: {value}" msgstr "" -#: ../../uaclient/messages/__init__.py:2149 +#: ../../uaclient/messages/__init__.py:2172 #, python-brace-format msgid "Ubuntu Pro server provided no aptURL directive for {entitlement_name}" msgstr "" -#: ../../uaclient/messages/__init__.py:2157 +#: ../../uaclient/messages/__init__.py:2180 #, python-brace-format msgid "" "This machine is not attached to an Ubuntu Pro subscription.\n" "See {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2166 +#: ../../uaclient/messages/__init__.py:2189 #, python-brace-format msgid "" -"To use '{{valid_service}}' you need an Ubuntu Pro subscription\n" -"Personal and community subscriptions are available at no charge\n" +"Cannot {{operation}} services when unattached - nothing to do.\n" +"To use '{{valid_service}}' you need an Ubuntu Pro subscription.\n" +"Personal and community subscriptions are available at no charge.\n" "See {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2182 +#: ../../uaclient/messages/__init__.py:2206 #, python-brace-format msgid "could not find entitlement named \"{entitlement_name}\"" msgstr "" -#: ../../uaclient/messages/__init__.py:2187 +#: ../../uaclient/messages/__init__.py:2211 msgid "failed to enable some services" msgstr "" -#: ../../uaclient/messages/__init__.py:2193 +#: ../../uaclient/messages/__init__.py:2216 +#, python-brace-format +msgid "failed to enable {service}" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2221 +#, python-brace-format +msgid "failed to disable {service}" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2227 msgid "Failed to enable default services, check: sudo pro status" msgstr "" -#: ../../uaclient/messages/__init__.py:2201 +#: ../../uaclient/messages/__init__.py:2235 msgid "Something went wrong during the attach process. Check the logs." msgstr "" -#: ../../uaclient/messages/__init__.py:2209 +#: ../../uaclient/messages/__init__.py:2243 #, python-brace-format msgid "Ubuntu Pro server provided no aptKey directive for {entitlement_name}" msgstr "" -#: ../../uaclient/messages/__init__.py:2216 +#: ../../uaclient/messages/__init__.py:2250 #, python-brace-format msgid "Ubuntu Pro server provided no suites directive for {entitlement_name}" msgstr "" -#: ../../uaclient/messages/__init__.py:2223 +#: ../../uaclient/messages/__init__.py:2257 #, python-brace-format msgid "" "Cannot setup apt pin. Empty apt repo origin value for {entitlement_name}" msgstr "" -#: ../../uaclient/messages/__init__.py:2232 +#: ../../uaclient/messages/__init__.py:2266 #, python-brace-format msgid "Could not determine contract delta service type {orig} {new}" msgstr "" -#: ../../uaclient/messages/__init__.py:2236 +#: ../../uaclient/messages/__init__.py:2272 #, python-brace-format msgid "" -"Error on Pro Image:\n" +"Cannot enable {service_being_enabled} when {required_service} is disabled.\n" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2280 +#, python-brace-format +msgid "" +"Cannot enable {service_being_enabled} when {incompatible_service} is enabled." +msgstr "" + +#: ../../uaclient/messages/__init__.py:2288 +#, python-brace-format +msgid "" +"Cannot disable {service_being_disabled} when {dependent_service} is " +"enabled.\n" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2297 +#, python-brace-format +msgid "" +"Failed to identify this image as a valid Ubuntu Pro image.\n" +"Details:\n" "{error_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2242 +#: ../../uaclient/messages/__init__.py:2307 #, python-brace-format msgid "" "An error occurred while talking the the cloud metadata service: {code} - " "{body}" msgstr "" -#: ../../uaclient/messages/__init__.py:2249 +#: ../../uaclient/messages/__init__.py:2314 #, python-brace-format msgid "" "Failed to attach machine\n" @@ -2836,41 +2927,41 @@ "For more information, see {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2259 +#: ../../uaclient/messages/__init__.py:2324 #, python-brace-format msgid "No valid AWS IMDS endpoint discovered at addresses: {addresses}" msgstr "" -#: ../../uaclient/messages/__init__.py:2266 +#: ../../uaclient/messages/__init__.py:2331 msgid "Unable to determine cloud platform." msgstr "" -#: ../../uaclient/messages/__init__.py:2274 +#: ../../uaclient/messages/__init__.py:2339 #, python-brace-format msgid "" "Auto-attach image support is not available on this image\n" "See: {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2283 +#: ../../uaclient/messages/__init__.py:2348 #, python-brace-format msgid "" "Auto-attach image support is not available on {{cloud_type}}\n" "See: {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2291 +#: ../../uaclient/messages/__init__.py:2356 #, python-brace-format msgid "{file_name} is not valid {file_format}" msgstr "" -#: ../../uaclient/messages/__init__.py:2297 +#: ../../uaclient/messages/__init__.py:2362 #, python-brace-format msgid "" "Could not parse /etc/os-release VERSION: {orig_ver} (modified to {mod_ver})" msgstr "" -#: ../../uaclient/messages/__init__.py:2305 +#: ../../uaclient/messages/__init__.py:2370 #, python-brace-format msgid "" "Could not extract series information from /etc/os-release.\n" @@ -2878,7 +2969,7 @@ "and the VERSION_CODENAME information is not present" msgstr "" -#: ../../uaclient/messages/__init__.py:2315 +#: ../../uaclient/messages/__init__.py:2380 #, python-brace-format msgid "" "There is a corrupted lock file in the system. To continue, please remove it\n" @@ -2887,189 +2978,226 @@ "$ sudo rm {lock_file_path}" msgstr "" -#: ../../uaclient/messages/__init__.py:2324 +#: ../../uaclient/messages/__init__.py:2389 #, python-brace-format msgid "{source} returned invalid json: {out}" msgstr "" -#: ../../uaclient/messages/__init__.py:2330 +#: ../../uaclient/messages/__init__.py:2395 #, python-brace-format msgid "" "Invalid value for {path_to_value} in /etc/ubuntu-advantage/uaclient.conf. " "Expected {expected_value}, found {value}." msgstr "" -#: ../../uaclient/messages/__init__.py:2339 +#: ../../uaclient/messages/__init__.py:2404 #, python-brace-format msgid "" "Cannot set {key} to {value}: for interval must be a positive integer." msgstr "" -#: ../../uaclient/messages/__init__.py:2346 +#: ../../uaclient/messages/__init__.py:2411 #, python-brace-format msgid "Invalid url in config. {key}: {value}" msgstr "" -#: ../../uaclient/messages/__init__.py:2351 +#: ../../uaclient/messages/__init__.py:2416 #, python-brace-format msgid "Could not find yaml file: {filepath}" msgstr "" -#: ../../uaclient/messages/__init__.py:2357 +#: ../../uaclient/messages/__init__.py:2422 msgid "" "Error: Setting global apt proxy and pro scoped apt proxy\n" "at the same time is unsupported.\n" "Cancelling config process operation.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:2367 +#: ../../uaclient/messages/__init__.py:2432 msgid "Can't load the distro-info database." msgstr "" -#: ../../uaclient/messages/__init__.py:2372 +#: ../../uaclient/messages/__init__.py:2437 #, python-brace-format msgid "Can't find series {series} in the distro-info database." msgstr "" -#: ../../uaclient/messages/__init__.py:2377 +#: ../../uaclient/messages/__init__.py:2442 #, python-brace-format msgid "Error: Cannot use {option1} together with {option2}." msgstr "" -#: ../../uaclient/messages/__init__.py:2381 +#: ../../uaclient/messages/__init__.py:2446 #, python-brace-format msgid "No help available for '{name}'" msgstr "" -#: ../../uaclient/messages/__init__.py:2387 +#: ../../uaclient/messages/__init__.py:2452 #, python-brace-format msgid "" "Error: issue \"{issue}\" is not recognized.\n" "Usage: \"pro fix CVE-yyyy-nnnn\" or \"pro fix USN-nnnn\"" msgstr "" -#: ../../uaclient/messages/__init__.py:2393 +#: ../../uaclient/messages/__init__.py:2458 #, python-brace-format msgid "{arg} must be one of: {choices}" msgstr "" -#: ../../uaclient/messages/__init__.py:2398 +#: ../../uaclient/messages/__init__.py:2463 +#, python-brace-format +msgid "Empty value provided for {arg}." +msgstr "" + +#: ../../uaclient/messages/__init__.py:2468 #, python-brace-format msgid "Expected {expected} but found: {actual}" msgstr "" -#: ../../uaclient/messages/__init__.py:2402 +#: ../../uaclient/messages/__init__.py:2472 msgid "Unable to process uaclient.conf" msgstr "" -#: ../../uaclient/messages/__init__.py:2407 +#: ../../uaclient/messages/__init__.py:2477 msgid "Unable to refresh your subscription" msgstr "" -#: ../../uaclient/messages/__init__.py:2412 +#: ../../uaclient/messages/__init__.py:2482 msgid "Unable to update Ubuntu Pro related APT and MOTD messages." msgstr "" -#: ../../uaclient/messages/__init__.py:2418 +#: ../../uaclient/messages/__init__.py:2488 msgid "json formatted response requires --assume-yes flag." msgstr "" -#: ../../uaclient/messages/__init__.py:2426 +#: ../../uaclient/messages/__init__.py:2496 msgid "" "Do not pass the TOKEN arg if you are using --attach-config.\n" "Include the token in the attach-config file instead.\n" " " msgstr "" -#: ../../uaclient/messages/__init__.py:2435 +#: ../../uaclient/messages/__init__.py:2505 msgid "Cannot provide both --args and --data at the same time" msgstr "" -#: ../../uaclient/messages/__init__.py:2441 +#: ../../uaclient/messages/__init__.py:2510 +msgid "Operation cancelled by user" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2516 #, python-brace-format msgid "Unable to perform: {lock_request}.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:2450 +#: ../../uaclient/messages/__init__.py:2525 msgid "This command must be run as root (try using sudo)." msgstr "" -#: ../../uaclient/messages/__init__.py:2455 +#: ../../uaclient/messages/__init__.py:2530 #, python-brace-format msgid "Metadata for {issue} is invalid. Error: {error_msg}." msgstr "" -#: ../../uaclient/messages/__init__.py:2462 +#: ../../uaclient/messages/__init__.py:2537 #, python-brace-format msgid "Error: {issue_id} not found." msgstr "" -#: ../../uaclient/messages/__init__.py:2466 +#: ../../uaclient/messages/__init__.py:2541 #, python-brace-format msgid "GPG key '{keyfile}' not found." msgstr "" -#: ../../uaclient/messages/__init__.py:2471 +#: ../../uaclient/messages/__init__.py:2546 #, python-brace-format msgid "'{endpoint}' is not a valid endpoint" msgstr "" -#: ../../uaclient/messages/__init__.py:2476 +#: ../../uaclient/messages/__init__.py:2551 #, python-brace-format msgid "Missing argument '{arg}' for endpoint {endpoint}" msgstr "" -#: ../../uaclient/messages/__init__.py:2481 +#: ../../uaclient/messages/__init__.py:2556 #, python-brace-format msgid "{endpoint} accepts no arguments" msgstr "" -#: ../../uaclient/messages/__init__.py:2486 +#: ../../uaclient/messages/__init__.py:2561 #, python-brace-format msgid "" "Error parsing API json data parameter:\n" "{data}" msgstr "" -#: ../../uaclient/messages/__init__.py:2491 +#: ../../uaclient/messages/__init__.py:2566 #, python-brace-format msgid "'{arg}' is not formatted as 'key=value'" msgstr "" -#: ../../uaclient/messages/__init__.py:2496 +#: ../../uaclient/messages/__init__.py:2571 #, python-brace-format msgid "Unable to determine version: {error_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2501 +#: ../../uaclient/messages/__init__.py:2576 msgid "features.disable_auto_attach set in config" msgstr "" -#: ../../uaclient/messages/__init__.py:2506 +#: ../../uaclient/messages/__init__.py:2581 #, python-brace-format msgid "Unable to determine unattended-upgrades status: {error_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2512 +#: ../../uaclient/messages/__init__.py:2587 #, python-brace-format msgid "Expected value with type {expected_type} but got type: {got_type}" msgstr "" -#: ../../uaclient/messages/__init__.py:2518 +#: ../../uaclient/messages/__init__.py:2593 #, python-brace-format msgid "" "Got value with incorrect type at index {index}:\n" "{nested_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2524 +#: ../../uaclient/messages/__init__.py:2599 #, python-brace-format msgid "" "Got value with incorrect type for field \"{key}\":\n" "{nested_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2531 +#: ../../uaclient/messages/__init__.py:2606 #, python-brace-format msgid "Value provided was not found in {enum_class}'s allowed: value: {values}" msgstr "" + +#: ../../uaclient/messages/__init__.py:2617 +#, python-brace-format +msgid "Error updating ESM services cache: {error}" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2623 +#, python-brace-format +msgid "" +"There is a problem with the resource directives provided by {url}\n" +"These entitlements: {names} are sharing the following directives\n" +" - APT url: {apt_url}\n" +" - Suite: {suite}\n" +"These directives need to be unique for every entitlement." +msgstr "" + +#: ../../uaclient/messages/__init__.py:2632 +msgid "landscape-config command failed" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2638 +msgid "" +"You must use the pro command to purge a service that has installed a kernel" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2645 +msgid "The operation is not supported" +msgstr "" diff -Nru ubuntu-advantage-tools-31.2.3~16.04/debian/rules ubuntu-advantage-tools-32~16.04/debian/rules --- ubuntu-advantage-tools-31.2.3~16.04/debian/rules 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/debian/rules 2024-04-23 13:37:02.000000000 +0000 @@ -60,10 +60,17 @@ override_dh_auto_install: dh_auto_install --destdir=debian/ubuntu-pro-client debian/jinja2_render debian/apparmor/ubuntu_pro_apt_news.jinja2 debian/apparmor/ubuntu_pro_apt_news ubuntu_codename=${UBUNTU_CODENAME} - # quick syntax check on the generated profile - apparmor_parser -K -T -Q debian/apparmor/ubuntu_pro_apt_news + debian/jinja2_render debian/apparmor/ubuntu_pro_esm_cache.jinja2 debian/apparmor/ubuntu_pro_esm_cache ubuntu_codename=${UBUNTU_CODENAME} + # quick syntax check on the generated profiles + mkdir debian/apparmor/local + touch debian/apparmor/local/ubuntu_pro_apt_news + touch debian/apparmor/local/ubuntu_pro_esm_cache + apparmor_parser -I $(CURDIR)/debian/apparmor -K -T -Q debian/apparmor/ubuntu_pro_apt_news + apparmor_parser -I $(CURDIR)/debian/apparmor -K -T -Q debian/apparmor/ubuntu_pro_esm_cache install -D -m 644 $(CURDIR)/debian/apparmor/ubuntu_pro_apt_news $(CURDIR)/debian/ubuntu-pro-client/etc/apparmor.d/ubuntu_pro_apt_news + install -D -m 644 $(CURDIR)/debian/apparmor/ubuntu_pro_esm_cache $(CURDIR)/debian/ubuntu-pro-client/etc/apparmor.d/ubuntu_pro_esm_cache dh_apparmor -pubuntu-pro-client --profile-name=ubuntu_pro_apt_news + dh_apparmor -pubuntu-pro-client --profile-name=ubuntu_pro_esm_cache flist=$$(find $(CURDIR)/debian/ -type f -name version.py) && sed -i 's,@@PACKAGED_VERSION@@,$(DEB_VERSION),' $${flist:-did-not-find-version-py-for-replacement} diff -Nru ubuntu-advantage-tools-31.2.3~16.04/debian/ubuntu-advantage-tools.postinst ubuntu-advantage-tools-32~16.04/debian/ubuntu-advantage-tools.postinst --- ubuntu-advantage-tools-31.2.3~16.04/debian/ubuntu-advantage-tools.postinst 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/debian/ubuntu-advantage-tools.postinst 2024-04-23 13:37:02.000000000 +0000 @@ -114,15 +114,16 @@ check_service_is_enabled() { service_name=$1 _RET=$(/usr/bin/python3 -c " -import os -import json -from uaclient.config import UAConfig -cfg = UAConfig() -status = cfg.read_cache('status-cache') -if status: - for service in status.get('services', []): - if service.get('name', '') == '${service_name}': - print(service.get('status', '')) +from uaclient.files.state_files import status_cache_file +try: + status = status_cache_file.read() + if status: + for service in status.get('services', []): + if service.get('name', '') == '${service_name}': + print(service.get('status', '')) +except Exception: + # Assume not enabled + pass ") if [ "${_RET}" = "enabled" ]; then return 0 @@ -473,6 +474,10 @@ if dpkg --compare-versions "$PREVIOUS_PKG_VER" lt "29~"; then rename_gpg_keys fi + + if dpkg --compare-versions "$PREVIOUS_PKG_VER" lt "31~"; then + /usr/lib/ubuntu-advantage/postinst-migrations.sh $PREVIOUS_PKG_VER + fi ;; esac diff -Nru ubuntu-advantage-tools-31.2.3~16.04/debian/ubuntu-pro-client.postinst ubuntu-advantage-tools-32~16.04/debian/ubuntu-pro-client.postinst --- ubuntu-advantage-tools-31.2.3~16.04/debian/ubuntu-pro-client.postinst 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/debian/ubuntu-pro-client.postinst 2024-05-10 17:07:05.000000000 +0000 @@ -41,26 +41,20 @@ fi } - case "$1" in configure) PREVIOUS_PKG_VER=$2 # - # Migrations from previous ubuntu-pro-client package versions - # - # These should always be version-gated using PREVIOUS_PKG_VER and execute in order from oldest to newest. - # For example: - # if dpkg --compare-versions "$PREVIOUS_PKG_VER" lt "33~"; then - # # do the migrations to version 33 - # fi - # if dpkg --compare-versions "$PREVIOUS_PKG_VER" lt "34~"; then - # # do the migrations to version 34 - # fi + # Migrations from previous ubuntu-pro-client package versions. + # These all exist in postinst-migrations.sh. + # See the explanation in that file. + # Do not add additional version migrations directly in this file. # - # none yet for ubuntu-pro-client package - + if dpkg --compare-versions "$PREVIOUS_PKG_VER" ge "31~"; then + /usr/lib/ubuntu-advantage/postinst-migrations.sh $PREVIOUS_PKG_VER + fi # # do-release-upgrade migrations from previous Ubuntu release ubuntu-pro-client package versions @@ -86,7 +80,7 @@ if grep -q "^ua_config:" /etc/ubuntu-advantage/uaclient.conf; then echo "Warning: uaclient.conf contains old ua_config field." >&2 echo " Please do the following:" >&2 - echo " 1. Run 'pro config set field=value' for each field/value pair" >&2 + echo " 1. Run 'sudo pro config set field=value' for each field/value pair" >&2 echo " present under ua_config in /etc/ubuntu-advantage/uaclient.conf" >&2 echo " 2. Delete ua_config and all sub-fields in" >&2 echo " /etc/ubuntu-advantage/uaclient.conf" >&2 diff -Nru ubuntu-advantage-tools-31.2.3~16.04/dev-requirements.txt ubuntu-advantage-tools-32~16.04/dev-requirements.txt --- ubuntu-advantage-tools-31.2.3~16.04/dev-requirements.txt 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/dev-requirements.txt 2024-04-23 13:37:02.000000000 +0000 @@ -1,6 +1,7 @@ -# The black, isort and shellcheck-py versions are also in .pre-commit-config.yaml; +# The black, isort, reformat-gherkin and shellcheck-py versions are also in .pre-commit-config.yaml; # make sure to update both together -black==22.3.0 +black==24.3.0 isort==5.12.0 pre-commit shellcheck-py==0.9.0.6 +reformat-gherkin==3.0.1 diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/_version.feature ubuntu-advantage-tools-32~16.04/features/_version.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/_version.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/_version.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,110 +1,123 @@ Feature: Pro is expected version - @uses.config.check_version - Scenario Outline: Check pro version - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `dpkg-query --showformat='${Version}' --show ubuntu-advantage-tools` with sudo - Then I will see the following on stdout - """ - $behave_var{version} - """ - When I run `pro version` with sudo - Then I will see the following on stdout - """ - $behave_var{version} - """ - # The following doesn't actually assert anything. It merely ensures that the output of - # apt-cache policy ubuntu-advantage-tools on the test machine is included in our test output. - # This is useful to manually verify the package is installed from the correct source e.g. -proposed. - When I check the apt-cache policy of ubuntu-advantage-tools - Then the apt-cache policy of ubuntu-advantage-tools is - """ - THIS GETS REPLACED AT RUNTIME VIA A HACK IN steps/ubuntu_advantage_tools.py - """ - Examples: version - | release | machine_type | - | xenial | lxd-container | - | xenial | lxd-vm | - | xenial | aws.generic | - | xenial | aws.pro | - | xenial | aws.pro-fips | - | xenial | azure.generic | - | xenial | azure.pro | - | xenial | azure.pro-fips | - | xenial | gcp.generic | - | xenial | gcp.pro | - | xenial | gcp.pro-fips | - | bionic | lxd-container | - | bionic | lxd-vm | - | bionic | aws.generic | - | bionic | aws.pro | - | bionic | aws.pro-fips | - | bionic | azure.generic | - | bionic | azure.pro | - | bionic | azure.pro-fips | - | bionic | gcp.generic | - | bionic | gcp.pro | - | bionic | gcp.pro-fips | - | focal | lxd-container | - | focal | lxd-vm | - | focal | aws.generic | - | focal | aws.pro | - | focal | aws.pro-fips | - | focal | azure.generic | - | focal | azure.pro | - | focal | azure.pro-fips | - | focal | gcp.generic | - | focal | gcp.pro | - | focal | gcp.pro-fips | - | jammy | lxd-container | - | jammy | lxd-vm | - | jammy | aws.generic | - | jammy | aws.pro | - | jammy | aws.pro-fips | - | jammy | azure.generic | - | jammy | azure.pro | - | jammy | azure.pro-fips | - | jammy | gcp.generic | - | jammy | gcp.pro | - | jammy | gcp.pro-fips | - | mantic | lxd-container | - | mantic | lxd-vm | - | mantic | aws.generic | - | mantic | aws.pro | - | mantic | aws.pro-fips | - | mantic | azure.generic | - | mantic | azure.pro | - | mantic | azure.pro-fips | - | mantic | gcp.generic | - | mantic | gcp.pro | - | mantic | gcp.pro-fips | + @uses.config.check_version + Scenario Outline: Check pro version + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `dpkg-query --showformat='${Version}' --show ubuntu-advantage-tools` with sudo + Then I will see the following on stdout + """ + $behave_var{version} + """ + When I run `pro version` with sudo + Then I will see the following on stdout + """ + $behave_var{version} + """ + # The following doesn't actually assert anything. It merely ensures that the output of + # apt-cache policy ubuntu-advantage-tools on the test machine is included in our test output. + # This is useful to manually verify the package is installed from the correct source e.g. -proposed. + When I check the apt-cache policy of ubuntu-advantage-tools + Then the apt-cache policy of ubuntu-advantage-tools is + """ + THIS GETS REPLACED AT RUNTIME VIA A HACK IN steps/ubuntu_advantage_tools.py + """ - @uses.config.check_version - @upgrade - Scenario Outline: Check pro version - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `dpkg-query --showformat='${Version}' --show ubuntu-advantage-tools` with sudo - Then I will see the following on stdout - """ - $behave_var{version} - """ - When I run `pro version` with sudo - Then I will see the following on stdout - """ - $behave_var{version} - """ - # The following doesn't actually assert anything. It merely ensures that the output of - # apt-cache policy ubuntu-advantage-tools on the test machine is included in our test output. - # This is useful to manually verify the package is installed from the correct source e.g. -proposed. - When I check the apt-cache policy of ubuntu-advantage-tools - Then the apt-cache policy of ubuntu-advantage-tools is - """ - THIS GETS REPLACED AT RUNTIME VIA A HACK IN steps/ubuntu_advantage_tools.py - """ - Examples: version - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | + Examples: version + | release | machine_type | + | xenial | lxd-container | + | xenial | lxd-vm | + | xenial | aws.generic | + | xenial | aws.pro | + | xenial | aws.pro-fips | + | xenial | azure.generic | + | xenial | azure.pro | + | xenial | azure.pro-fips | + | xenial | gcp.generic | + | xenial | gcp.pro | + | xenial | gcp.pro-fips | + | bionic | lxd-container | + | bionic | lxd-vm | + | bionic | aws.generic | + | bionic | aws.pro | + | bionic | aws.pro-fips | + | bionic | azure.generic | + | bionic | azure.pro | + | bionic | azure.pro-fips | + | bionic | gcp.generic | + | bionic | gcp.pro | + | bionic | gcp.pro-fips | + | focal | lxd-container | + | focal | lxd-vm | + | focal | aws.generic | + | focal | aws.pro | + | focal | aws.pro-fips | + | focal | azure.generic | + | focal | azure.pro | + | focal | azure.pro-fips | + | focal | gcp.generic | + | focal | gcp.pro | + | focal | gcp.pro-fips | + | jammy | lxd-container | + | jammy | lxd-vm | + | jammy | aws.generic | + | jammy | aws.pro | + | jammy | aws.pro-fips | + | jammy | azure.generic | + | jammy | azure.pro | + | jammy | azure.pro-fips | + | jammy | gcp.generic | + | jammy | gcp.pro | + | jammy | gcp.pro-fips | + | mantic | lxd-container | + | mantic | lxd-vm | + | mantic | aws.generic | + | mantic | aws.pro | + | mantic | aws.pro-fips | + | mantic | azure.generic | + | mantic | azure.pro | + | mantic | azure.pro-fips | + | mantic | gcp.generic | + | mantic | gcp.pro | + | mantic | gcp.pro-fips | + | noble | lxd-container | + | noble | lxd-vm | + | noble | aws.generic | + | noble | aws.pro | + | noble | aws.pro-fips | + | noble | azure.generic | + | noble | azure.pro | + | noble | azure.pro-fips | + | noble | gcp.generic | + | noble | gcp.pro | + | noble | gcp.pro-fips | + + @uses.config.check_version @upgrade + Scenario Outline: Check pro version + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `dpkg-query --showformat='${Version}' --show ubuntu-advantage-tools` with sudo + Then I will see the following on stdout + """ + $behave_var{version} + """ + When I run `pro version` with sudo + Then I will see the following on stdout + """ + $behave_var{version} + """ + # The following doesn't actually assert anything. It merely ensures that the output of + # apt-cache policy ubuntu-advantage-tools on the test machine is included in our test output. + # This is useful to manually verify the package is installed from the correct source e.g. -proposed. + When I check the apt-cache policy of ubuntu-advantage-tools + Then the apt-cache policy of ubuntu-advantage-tools is + """ + THIS GETS REPLACED AT RUNTIME VIA A HACK IN steps/ubuntu_advantage_tools.py + """ + + Examples: version + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/airgapped.feature ubuntu-advantage-tools-32~16.04/features/airgapped.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/airgapped.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/airgapped.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,49 +1,99 @@ @uses.config.contract_token Feature: Performing attach using ua-airgapped - Scenario Outline: Pro works with the airgapped contract server - Given a `` `` machine with ubuntu-advantage-tools installed - # set up the apt mirror configuration - Given a `jammy` `` machine named `mirror` - When I run `add-apt-repository ppa:yellow/ua-airgapped -y` `with sudo` on the `mirror` machine - And I apt update on the `mirror` machine - And I apt install `apt-mirror get-resource-tokens ua-airgapped` on the `mirror` machine - And I download the service credentials on the `mirror` machine - And I extract the `esm-infra` credentials from the `mirror` machine - And I extract the `esm-apps` credentials from the `mirror` machine - And I set the apt-mirror file for `` with the `esm-infra,esm-apps` credentials on the `mirror` machine - And I run `apt-mirror` `with sudo` on the `mirror` machine - And I serve the `esm-infra` mirror using port `8000` on the `mirror` machine - And I serve the `esm-apps` mirror using port `9000` on the `mirror` machine - # set up the ua-airgapped configuration - And I create the contract config overrides file for `esm-infra,esm-apps` on the `mirror` machine - And I generate the contracts-airgapped configuration on the `mirror` machine - # set up the contracts-airgapped configuration - Given a `jammy` `` machine named `contracts` - When I run `add-apt-repository ppa:yellow/ua-airgapped -y` `with sudo` on the `contracts` machine - And I apt update on the `contracts` machine - And I apt install `contracts-airgapped` on the `contracts` machine - And I run `apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4067E40313CB4B13` `with sudo` on the `contracts` machine - And I disable any internet connection on the `contracts` machine - And I send the contracts-airgapped config from the `mirror` machine to the `contracts` machine - And I start the contracts-airgapped service on the `contracts` machine - # attach an airgapped machine to the contracts-airgapped server - And I disable any internet connection on the machine - And I change config key `contract_url` to use value `http://$behave_var{machine-ip contracts}:8484` - And I attach `contract_token` with sudo - Then I verify that `esm-infra` is enabled - And I verify that `esm-apps` is enabled - When I run `apt-cache policy hello` with sudo - Then stdout matches regexp: - """ - 510 .*:9000/ubuntu jammy-apps-security/main - """ - And stdout matches regexp: - """ - 510 .*:8000/ubuntu jammy-infra-security/main - """ - Then I verify that running `pro refresh` `with sudo` exits `0` + Scenario Outline: Pro works with the airgapped contract server + Given a `` `` machine with ubuntu-advantage-tools installed + # set up the apt mirror configuration + Given a `jammy` `` machine named `mirror` + When I run `add-apt-repository ppa:yellow/ua-airgapped -y` `with sudo` on the `mirror` machine + And I apt update on the `mirror` machine + And I apt install `apt-mirror get-resource-tokens ua-airgapped` on the `mirror` machine + And I download the service credentials on the `mirror` machine + And I extract the `esm-infra` credentials from the `mirror` machine + And I extract the `esm-apps` credentials from the `mirror` machine + And I set the apt-mirror file for `` with the `esm-infra,esm-apps` credentials on the `mirror` machine + And I run `apt-mirror` `with sudo` on the `mirror` machine + And I serve the `esm-infra` mirror using port `8000` on the `mirror` machine + And I serve the `esm-apps` mirror using port `9000` on the `mirror` machine + # set up the ua-airgapped configuration + And I create the contract config overrides file for `esm-infra,esm-apps` on the `mirror` machine + And I generate the contracts-airgapped configuration on the `mirror` machine + # set up the contracts-airgapped configuration + Given a `jammy` `` machine named `contracts` + When I run `add-apt-repository ppa:yellow/ua-airgapped -y` `with sudo` on the `contracts` machine + And I apt update on the `contracts` machine + And I apt install `contracts-airgapped` on the `contracts` machine + And I run `apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4067E40313CB4B13` `with sudo` on the `contracts` machine + And I disable any internet connection on the `contracts` machine + And I send the contracts-airgapped config from the `mirror` machine to the `contracts` machine + And I start the contracts-airgapped service on the `contracts` machine + # attach an airgapped machine to the contracts-airgapped server + And I disable any internet connection on the machine + And I change config key `contract_url` to use value `http://$behave_var{machine-ip contracts}:8484` + And I attach `contract_token` with sudo + Then I verify that `esm-infra` is enabled + And I verify that `esm-apps` is enabled + When I run `apt-cache policy hello` with sudo + Then stdout matches regexp: + """ + 510 .*:9000/ubuntu jammy-apps-security/main + """ + And stdout matches regexp: + """ + 510 .*:8000/ubuntu jammy-infra-security/main + """ + Then I verify that running `pro refresh` `with sudo` exits `0` - Examples: ubuntu release - | release | machine_type | - | jammy | lxd-container | + Examples: ubuntu release + | release | machine_type | + | jammy | lxd-container | + + Scenario Outline: airgapped environment with same apt url for different services + Given a `` `` machine with ubuntu-advantage-tools installed + # set up the apt mirror configuration + Given a `jammy` `` machine named `mirror` + When I run `add-apt-repository ppa:yellow/ua-airgapped -y` `with sudo` on the `mirror` machine + And I run `apt-get update` `with sudo` on the `mirror` machine + And I run `apt-get install apt-mirror get-resource-tokens ua-airgapped -yq` `with sudo` on the `mirror` machine + And I download the service credentials on the `mirror` machine + And I extract the `esm-infra` credentials from the `mirror` machine + And I extract the `esm-apps` credentials from the `mirror` machine + And I set the apt-mirror file for `` with the `esm-infra,esm-apps` credentials on the `mirror` machine + And I run `apt-mirror` `with sudo` on the `mirror` machine + And I consolidate `esm-infra,esm-apps` on a single mirror on the `mirror` machine + And I serve the `all-mirrors` mirror using port `8000` on the `mirror` machine + # set up the ua-airgapped configuration + And I create the contract config overrides file for `esm-infra,esm-apps` on the `mirror` machine + And I generate the contracts-airgapped configuration on the `mirror` machine + # set up the contracts-airgapped configuration + Given a `jammy` `` machine named `contracts` + When I run `add-apt-repository ppa:yellow/ua-airgapped -y` `with sudo` on the `contracts` machine + And I run `apt-get update` `with sudo` on the `contracts` machine + And I run `apt-get install contracts-airgapped -yq` `with sudo` on the `contracts` machine + And I run `apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4067E40313CB4B13` `with sudo` on the `contracts` machine + And I disable any internet connection on the `contracts` machine + And I send the contracts-airgapped config from the `mirror` machine to the `contracts` machine + And I start the contracts-airgapped service on the `contracts` machine + # attach an airgapped machine to the contracts-airgapped server + And I disable any internet connection on the machine + And I change config key `contract_url` to use value `http://$behave_var{machine-ip contracts}:8484` + And I attach `contract_token` with sudo + Then stdout matches regexp: + """ + esm-apps +yes +enabled .* + esm-infra +yes +enabled .* + """ + When I run `apt-cache policy hello` with sudo + Then stdout matches regexp: + """ + 510 .*:8000/ubuntu jammy-apps-security/main + """ + And stdout matches regexp: + """ + 510 .*:8000/ubuntu jammy-infra-security/main + """ + Then I verify that running `pro refresh` `with sudo` exits `0` + + Examples: ubuntu release + | release | machine_type | + | jammy | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/anbox.feature ubuntu-advantage-tools-32~16.04/features/anbox.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/anbox.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/anbox.feature 2024-05-10 17:07:05.000000000 +0000 @@ -1,116 +1,122 @@ @uses.config.contract_token Feature: Enable anbox on Ubuntu - Scenario Outline: Enable Anbox cloud service in a container - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo and options `--no-auto-enable` - Then I verify that `anbox-cloud` is disabled - Then I verify that running `pro enable anbox-cloud` `as non-root` exits `1` - And I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - When I verify that running `pro enable anbox-cloud` `with sudo` exits `1` - Then I will see the following on stdout: - """ - One moment, checking your subscription first - It is only possible to enable Anbox Cloud on a container using - the --access-only flag. - """ - When I run `pro enable anbox-cloud --access-only` with sudo - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Updating Anbox Cloud package lists - Anbox Cloud access enabled - """ - And I verify that `anbox-cloud` is enabled - When I run `apt-cache policy` with sudo - Then apt-cache policy for the following url has priority `500` - """ - https://archive.anbox-cloud.io/stable /main amd64 Packages - """ - When I run `pro disable anbox-cloud` with sudo - Then I verify that `anbox-cloud` is disabled - - Examples: ubuntu release - | release | machine_type | - | jammy | lxd-container | - - Scenario Outline: Enable Anbox cloud service in an unsupported release - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo and options `--no-auto-enable` - And I verify that running `pro enable anbox-cloud` `with sudo` exits `1` - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Anbox Cloud is not available for Ubuntu 16.04 LTS (Xenial Xerus). - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-vm | - - Scenario Outline: Enable Anbox cloud service in a VM - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo and options `--no-auto-enable` - And I run `snap remove lxd` with sudo - And I run `pro enable anbox-cloud --access-only --assume-yes` with sudo - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Updating Anbox Cloud package lists - Anbox Cloud access enabled - """ - And I verify that `anbox-cloud` is enabled - When I run `apt-cache policy` with sudo - Then apt-cache policy for the following url has priority `500` - """ - https://archive.anbox-cloud.io/stable /main amd64 Packages - """ - And I check that snap `amc` is not installed - And I check that snap `lxd` is not installed - And I check that snap `anbox-cloud-appliance` is not installed - And I verify that files exist matching `/var/lib/ubuntu-advantage/private/anbox-cloud-credentials` - When I run `cat /var/lib/ubuntu-advantage/private/anbox-cloud-credentials` with sudo - Then stdout is a json matching the `anbox_cloud_credentials` schema - When I run `pro disable anbox-cloud` with sudo - Then I verify that `anbox-cloud` is disabled - And I verify that no files exist matching `/var/lib/ubuntu-advantage/private/anbox-cloud-credentials` - When I run `pro enable anbox-cloud --assume-yes` with sudo - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Installing required snaps - Installing required snap: amc - Installing required snap: anbox-cloud-appliance - Installing required snap: lxd - Updating Anbox Cloud package lists - Anbox Cloud enabled - To finish setting up the Anbox Cloud Appliance, run: - - $ sudo anbox-cloud-appliance init - - You can accept the default answers if you do not have any specific - configuration changes. - For more information, see https://anbox-cloud.io/docs/tut/installing-appliance#initialise - """ - Then I verify that `anbox-cloud` is enabled - When I run `apt-cache policy` with sudo - Then apt-cache policy for the following url has priority `500` - """ - https://archive.anbox-cloud.io/stable /main amd64 Packages - """ - And I check that snap `amc` is installed - And I check that snap `lxd` is installed - And I check that snap `anbox-cloud-appliance` is installed - And I verify that files exist matching `/var/lib/ubuntu-advantage/private/anbox-cloud-credentials` - When I run `cat /var/lib/ubuntu-advantage/private/anbox-cloud-credentials` with sudo - Then stdout is a json matching the `anbox_cloud_credentials` schema - When I run `pro disable anbox-cloud` with sudo - Then I verify that `anbox-cloud` is disabled - And I verify that no files exist matching `/var/lib/ubuntu-advantage/private/anbox-cloud-credentials` - - Examples: ubuntu release - | release | machine_type | - | jammy | lxd-vm | + Scenario Outline: Enable Anbox cloud service in a container + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo and options `--no-auto-enable` + Then I verify that `anbox-cloud` is disabled + Then I verify that running `pro enable anbox-cloud` `as non-root` exits `1` + And I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + When I verify that running `pro enable anbox-cloud` `with sudo` exits `1` + Then I will see the following on stdout: + """ + One moment, checking your subscription first + It is only possible to enable Anbox Cloud on a container using + the --access-only flag. + Could not enable Anbox Cloud. + """ + When I run `pro enable anbox-cloud --access-only` with sudo + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Configuring APT access to Anbox Cloud + Updating Anbox Cloud package lists + Anbox Cloud access enabled + """ + And I verify that `anbox-cloud` is enabled + When I run `apt-cache policy` with sudo + Then apt-cache policy for the following url has priority `500` + """ + https://archive.anbox-cloud.io/stable /main amd64 Packages + """ + When I run `pro disable anbox-cloud` with sudo + Then I verify that `anbox-cloud` is disabled + + Examples: ubuntu release + | release | machine_type | + | jammy | lxd-container | + | noble | lxd-container | + + Scenario Outline: Enable Anbox cloud service in an unsupported release + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo and options `--no-auto-enable` + And I verify that running `pro enable anbox-cloud` `with sudo` exits `1` + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Anbox Cloud is not available for Ubuntu 16.04 LTS (Xenial Xerus). + Could not enable Anbox Cloud. + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-vm | + + Scenario Outline: Enable Anbox cloud service in a VM + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo and options `--no-auto-enable` + And I run `snap remove lxd` with sudo + And I run `pro enable anbox-cloud --access-only --assume-yes` with sudo + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Configuring APT access to Anbox Cloud + Updating Anbox Cloud package lists + Anbox Cloud access enabled + """ + And I verify that `anbox-cloud` is enabled + When I run `apt-cache policy` with sudo + Then apt-cache policy for the following url has priority `500` + """ + https://archive.anbox-cloud.io/stable /main amd64 Packages + """ + And I check that snap `amc` is not installed + And I check that snap `lxd` is not installed + And I check that snap `anbox-cloud-appliance` is not installed + And I verify that files exist matching `/var/lib/ubuntu-advantage/private/anbox-cloud-credentials` + When I run `cat /var/lib/ubuntu-advantage/private/anbox-cloud-credentials` with sudo + Then stdout is a json matching the `anbox_cloud_credentials` schema + When I run `pro disable anbox-cloud` with sudo + Then I verify that `anbox-cloud` is disabled + And I verify that no files exist matching `/var/lib/ubuntu-advantage/private/anbox-cloud-credentials` + When I run `pro enable anbox-cloud --assume-yes` with sudo + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Installing required snaps + Installing required snap: amc + Installing required snap: anbox-cloud-appliance + Installing required snap: lxd + Updating Anbox Cloud package lists + Anbox Cloud enabled + To finish setting up the Anbox Cloud Appliance, run: + + $ sudo anbox-cloud-appliance init + + You can accept the default answers if you do not have any specific + configuration changes. + For more information, see https://anbox-cloud.io/docs/tut/installing-appliance#initialise + """ + Then I verify that `anbox-cloud` is enabled + When I run `apt-cache policy` with sudo + Then apt-cache policy for the following url has priority `500` + """ + https://archive.anbox-cloud.io/stable /main amd64 Packages + """ + And I check that snap `amc` is installed + And I check that snap `lxd` is installed + And I check that snap `anbox-cloud-appliance` is installed + And I verify that files exist matching `/var/lib/ubuntu-advantage/private/anbox-cloud-credentials` + When I run `cat /var/lib/ubuntu-advantage/private/anbox-cloud-credentials` with sudo + Then stdout is a json matching the `anbox_cloud_credentials` schema + When I run `pro disable anbox-cloud` with sudo + Then I verify that `anbox-cloud` is disabled + And I verify that no files exist matching `/var/lib/ubuntu-advantage/private/anbox-cloud-credentials` + + Examples: ubuntu release + | release | machine_type | + | jammy | lxd-vm | + | noble | lxd-vm | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/api.feature ubuntu-advantage-tools-32~16.04/features/api.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/api.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/api.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,6 +1,6 @@ Feature: Client behaviour for the API endpoints - Scenario Outline: all API endpoints can be imported individually + Scenario Outline: all API endpoints can be imported individually Given a `` `` machine with ubuntu-advantage-tools installed When I run `python3 -c "from uaclient.api.u.pro.attach.auto.configure_retry_service.v1 import configure_retry_service"` as non-root When I run `python3 -c "from uaclient.api.u.pro.attach.auto.full_auto_attach.v1 import full_auto_attach"` as non-root @@ -16,74 +16,273 @@ When I run `python3 -c "from uaclient.api.u.pro.security.fix.usn.plan.v1 import plan"` as non-root When I run `python3 -c "from uaclient.api.u.pro.security.status.livepatch_cves.v1 import livepatch_cves"` as non-root When I run `python3 -c "from uaclient.api.u.pro.security.status.reboot_required.v1 import reboot_required"` as non-root + When I run `python3 -c "from uaclient.api.u.pro.services.dependencies.v1 import dependencies"` as non-root When I run `python3 -c "from uaclient.api.u.pro.status.enabled_services.v1 import enabled_services"` as non-root When I run `python3 -c "from uaclient.api.u.pro.status.is_attached.v1 import is_attached"` as non-root When I run `python3 -c "from uaclient.api.u.pro.version.v1 import version"` as non-root When I run `python3 -c "from uaclient.api.u.security.package_manifest.v1 import package_manifest"` as non-root When I run `python3 -c "from uaclient.api.u.unattended_upgrades.status.v1 import status"` as non-root When I run `python3 -c "from uaclient.api.u.apt_news.current_news.v1 import current_news"` as non-root + When I run `python3 -c "from uaclient.api.u.pro.detach.v1 import detach"` as non-root Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | - Scenario Outline: API invalid endpoint or args + Scenario Outline: API invalid endpoint or args Given a `` `` machine with ubuntu-advantage-tools installed When I verify that running `pro api invalid.endpoint` `with sudo` exits `1` - Then stdout matches regexp: - """ - {\"_schema_version\": \"v1\", \"data\": {\"meta\": {\"environment_vars\": \[]}}, \"errors\": \[{\"code\": \"api\-invalid\-endpoint", \"meta\": {\"endpoint\": \"invalid.endpoint\"}, \"title\": \"'invalid\.endpoint' is not a valid endpoint\"}], \"result\": \"failure\", \"version\": \".*\", \"warnings\": \[]} - """ + Then API full output matches regexp: + """ + { + "_schema_version": "v1", + "data": { + "meta": { + "environment_vars": [] + } + }, + "errors": [ + { + "code": "api-invalid-endpoint", + "meta": { + "endpoint": "invalid\.endpoint" + }, + "title": "'invalid\.endpoint' is not a valid endpoint" + } + ], + "result": "failure", + "version": ".*", + "warnings": [] + } + """ When I verify that running `pro api u.pro.version.v1 --args extra=arg` `with sudo` exits `1` - Then stdout matches regexp: - """ - {\"_schema_version\": \"v1\", \"data\": {\"meta\": {\"environment_vars\": \[]}}, \"errors\": \[{\"code\": \"api\-no\-argument\-for\-endpoint\", \"meta\": {\"endpoint\": \"u.pro.version.v1\"}, \"title\": \"u\.pro\.version\.v1 accepts no arguments\"}], \"result\": \"failure\", \"version\": \".*\", \"warnings\": \[]} - """ + Then API errors field output matches regexp: + """ + [ + { + "code": "api-no-argument-for-endpoint", + "meta": { + "endpoint": "u.pro.version.v1" + }, + "title": "u.pro.version.v1 accepts no arguments" + } + ] + """ Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | - Scenario Outline: Basic endpoints + Scenario Outline: Basic endpoints Given a `` `` machine with ubuntu-advantage-tools installed When I run `pro api u.pro.version.v1` with sudo - Then stdout matches regexp: - """ - {\"_schema_version\": \"v1\", \"data\": {\"attributes\": {\"installed_version\": \".*\"}, \"meta\": {\"environment_vars\": \[]}, \"type\": \"Version\"}, \"errors\": \[], \"result\": \"success\", \"version\": \".*\", \"warnings\": \[]} - """ + Then API full output matches regexp: + """ + { + "_schema_version": "v1", + "data": { + "attributes": { + "installed_version": ".*" + }, + "meta": { + "environment_vars": [] + }, + "type": "Version" + }, + "errors": [], + "result": "success", + "version": ".*", + "warnings": [] + } + """ When I run `UA_LOG_FILE=/tmp/some_file OTHER_ENVVAR=not_there pro api u.pro.version.v1` with sudo - Then stdout matches regexp: - """ - {\"_schema_version\": \"v1\", \"data\": {\"attributes\": {\"installed_version\": \".*\"}, \"meta\": {\"environment_vars\": \[{\"name\": \"UA_LOG_FILE\", \"value\": \"\/tmp\/some_file\"}]}, \"type\": \"Version\"}, \"errors\": \[], \"result\": \"success\", \"version\": \".*\", \"warnings\": \[]} - """ - When I run `ua api u.pro.attach.auto.should_auto_attach.v1` with sudo - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"should_auto_attach": false}, "meta": {"environment_vars": \[\]}, "type": "ShouldAutoAttach"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `ua api u.pro.status.is_attached.v1` with sudo - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"is_attached": false}, "meta": {"environment_vars": \[\]}, "type": "IsAttached"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `ua api u.pro.status.enabled_services.v1` with sudo - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"enabled_services": \[\]}, "meta": {"environment_vars": \[\]}, "type": "EnabledServices"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ + Then API data field output matches regexp: + """ + { + "attributes": { + "installed_version": ".*" + }, + "meta": { + "environment_vars": [ + { + "name": "UA_LOG_FILE", + "value": "/tmp/some_file" + } + ] + }, + "type": "Version" + } + """ + When I run `pro api u.pro.attach.auto.should_auto_attach.v1` with sudo + Then API data field output matches regexp: + """ + { + "attributes": { + "should_auto_attach": false + }, + "meta": { + "environment_vars": [] + }, + "type": "ShouldAutoAttach" + } + """ + When I run `pro api u.pro.status.is_attached.v1` with sudo + Then API data field output matches regexp: + """ + { + "attributes": { + "contract_remaining_days": 0, + "contract_status": null, + "is_attached": false, + "is_attached_and_contract_valid": false + }, + "meta": { + "environment_vars": [] + }, + "type": "IsAttached" + } + """ + When I run `pro api u.pro.status.enabled_services.v1` with sudo + Then API data field output matches regexp: + """ + { + "attributes": { + "enabled_services": [] + }, + "meta": { + "environment_vars": [] + }, + "type": "EnabledServices" + } + """ Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + @uses.config.contract_token + Scenario Outline: u.pro.status.is_attached.v1 + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro api u.pro.status.is_attached.v1` with sudo + Then API data field output matches regexp: + """ + { + "attributes": { + "contract_remaining_days": 0, + "contract_status": null, + "is_attached": false, + "is_attached_and_contract_valid": false + }, + "meta": { + "environment_vars": [] + }, + "type": "IsAttached" + } + """ + When I attach `contract_token` with sudo + And I run `pro api u.pro.status.is_attached.v1` with sudo + Then API data field output matches regexp: + """ + { + "attributes": { + "contract_remaining_days": \d+, + "contract_status": "active", + "is_attached": true, + "is_attached_and_contract_valid": true + }, + "meta": { + "environment_vars": [] + }, + "type": "IsAttached" + } + """ + When I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + effectiveTo: $behave_var{today +2} + """ + And I run `ua api u.pro.status.is_attached.v1` with sudo + Then API data field output matches regexp: + """ + { + "attributes": { + "contract_remaining_days": 2, + "contract_status": "active-soon-to-expire", + "is_attached": true, + "is_attached_and_contract_valid": true + }, + "meta": { + "environment_vars": [] + }, + "type": "IsAttached" + } + """ + When I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + effectiveTo: $behave_var{today -2} + """ + And I run `pro api u.pro.status.is_attached.v1` with sudo + Then API data field output matches regexp: + """ + { + "attributes": { + "contract_remaining_days": -2, + "contract_status": "grace-period", + "is_attached": true, + "is_attached_and_contract_valid": true + }, + "meta": { + "environment_vars": [] + }, + "type": "IsAttached" + } + """ + When I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + effectiveTo: $behave_var{today -50} + """ + And I run `pro api u.pro.status.is_attached.v1` with sudo + Then API data field output matches regexp: + """ + { + "attributes": { + "contract_remaining_days": -50, + "contract_status": "expired", + "is_attached": true, + "is_attached_and_contract_valid": false + }, + "meta": { + "environment_vars": [] + }, + "type": "IsAttached" + } + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/api_configure_retry_service.feature ubuntu-advantage-tools-32~16.04/features/api_configure_retry_service.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/api_configure_retry_service.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/api_configure_retry_service.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,56 +1,60 @@ Feature: api.u.pro.attach.auto.configure_retry_service - Scenario Outline: v1 successfully triggers retry service when run during startup - Given a `` `` machine with ubuntu-advantage-tools installed - When I change contract to staging with sudo - When I create the file `/lib/systemd/system/apitest.service` with the following - """ - [Unit] - Description=test - Before=ubuntu-advantage.service + Scenario Outline: v1 successfully triggers retry service when run during startup + Given a `` `` machine with ubuntu-advantage-tools installed + When I change contract to staging with sudo + When I create the file `/lib/systemd/system/apitest.service` with the following + """ + [Unit] + Description=test + Before=ubuntu-advantage.service - [Service] - Type=oneshot - ExecStart=/usr/bin/pro api u.pro.attach.auto.configure_retry_service.v1 + [Service] + Type=oneshot + ExecStart=/usr/bin/pro api u.pro.attach.auto.configure_retry_service.v1 - [Install] - WantedBy=multi-user.target - """ - When I run `systemctl enable apitest.service` with sudo - When I reboot the machine - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `0` - Then stdout matches regexp: - """ - Active: active \(running\) - """ - Then stdout matches regexp: - """ - mode: retry auto attach - """ - Then stdout does not match regexp: - """ - mode: poll for pro license - """ - When I run `run-parts /etc/update-motd.d/` with sudo - Then stdout matches regexp: - """ - Failed to automatically attach to an Ubuntu Pro subscription 1 time\(s\). - The failure was due to: an unknown error. - The next attempt is scheduled for \d+-\d+-\d+T\d+:\d+:00.*. - You can try manually with `sudo pro auto-attach`. - """ - When I run `pro status` with sudo - Then stdout matches regexp: - """ - NOTICES - Failed to automatically attach to an Ubuntu Pro subscription 1 time\(s\). - The failure was due to: an unknown error. - The next attempt is scheduled for \d+-\d+-\d+T\d+:\d+:00.*. - You can try manually with `sudo pro auto-attach`. - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | + [Install] + WantedBy=multi-user.target + """ + When I run `systemctl enable apitest.service` with sudo + When I reboot the machine + # Cloud init may take a while here + And I wait `15` seconds + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `0` + Then stdout matches regexp: + """ + Active: active \(running\) + """ + Then stdout matches regexp: + """ + mode: retry auto attach + """ + Then stdout does not match regexp: + """ + mode: poll for pro license + """ + When I run `run-parts /etc/update-motd.d/` with sudo + Then stdout matches regexp: + """ + Failed to automatically attach to an Ubuntu Pro subscription 1 time\(s\). + The failure was due to: an unknown error. + The next attempt is scheduled for \d+-\d+-\d+T\d+:\d+:00.*. + You can try manually with `sudo pro auto-attach`. + """ + When I run `pro status` with sudo + Then stdout matches regexp: + """ + NOTICES + Failed to automatically attach to an Ubuntu Pro subscription 1 time\(s\). + The failure was due to: an unknown error. + The next attempt is scheduled for \d+-\d+-\d+T\d+:\d+:00.*. + You can try manually with `sudo pro auto-attach`. + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/api_detach.feature ubuntu-advantage-tools-32~16.04/features/api_detach.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/api_detach.feature 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/api_detach.feature 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,112 @@ +Feature: Detach API endpoint + + @uses.config.contract_token + Scenario Outline: Detach API endpoint on an attached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify that running `pro api u.pro.detach.v1` `as non-root` exits `1` + Then stdout is a json matching the `api_response` schema + And API full output matches regexp: + """ + { + "_schema_version": "v1", + "data": { + "meta": { + "environment_vars": [] + } + }, + "errors": [ + { + "code": "nonroot-user", + "meta": {}, + "title": "This command must be run as root (try using sudo)." + } + ], + "result": "failure", + "version": ".*", + "warnings": [] + } + """ + When I run `pro api u.pro.detach.v1` with sudo + Then stdout is a json matching the `api_response` schema + And API full output matches regexp: + """ + { + "_schema_version": "v1", + "data": { + "attributes": { + "disabled": [], + "reboot_required": false + }, + "meta": { + "environment_vars": [] + }, + "type": "Detach" + }, + "errors": [], + "result": "success", + "version": ".*", + "warnings": [] + } + """ + When I attach `contract_token` with sudo + And I run `pro api u.pro.detach.v1` with sudo + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `detach` schema + And API full output matches regexp: + """ + { + "_schema_version": "v1", + "data": { + "attributes": { + "disabled": [ + "esm-apps", + "esm-infra" + ], + "reboot_required": false + }, + "meta": { + "environment_vars": [] + }, + "type": "Detach" + }, + "errors": [], + "result": "success", + "version": ".*", + "warnings": [] + } + """ + When I attach `contract_token` with sudo + And I run `touch /var/run/reboot-required` with sudo + And I run `pro api u.pro.detach.v1` with sudo + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `detach` schema + And API full output matches regexp: + """ + { + "_schema_version": "v1", + "data": { + "attributes": { + "disabled": [ + "esm-apps", + "esm-infra" + ], + "reboot_required": true + }, + "meta": { + "environment_vars": [] + }, + "type": "Detach" + }, + "errors": [], + "result": "success", + "version": ".*", + "warnings": [] + } + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/api_disable.feature ubuntu-advantage-tools-32~16.04/features/api_disable.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/api_disable.feature 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/api_disable.feature 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,203 @@ +Feature: u.pro.services.disable + + Scenario Outline: u.pro.services.disable.v1 container services + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt update + # Requires attach + When I verify that running `pro api u.pro.services.disable.v1 --args service=esm-infra` `with sudo` exits `1` + Then API errors field output is: + """ + [ + { + "code": "unattached", + "meta": {}, + "title": "This machine is not attached to an Ubuntu Pro subscription.\nSee https://ubuntu.com/pro" + } + ] + """ + When I attach `contract_token` with sudo + # Requires root + When I verify that running `pro api u.pro.services.disable.v1 --args service=esm-infra` `as non-root` exits `1` + Then API errors field output is: + """ + [ + { + "code": "nonroot-user", + "meta": {}, + "title": "This command must be run as root (try using sudo)." + } + ] + """ + # Invalid service name + When I verify that running `pro api u.pro.services.disable.v1 --args service=invalid` `with sudo` exits `1` + Then API errors field output is: + """ + [ + { + "code": "entitlement-not-found", + "meta": { + "entitlement_name": "invalid" + }, + "title": "could not find entitlement named \"invalid\"" + } + ] + """ + # Basic disable + When I run `pro api u.pro.services.disable.v1 --args service=esm-infra` with sudo + Then API data field output is: + """ + { + "attributes": { + "disabled": [ + "esm-infra" + ] + }, + "meta": { + "environment_vars": [] + }, + "type": "DisableService" + } + """ + Then I verify that `esm-infra` is disabled + # Disable already disabled service succeeds + When I run `pro api u.pro.services.disable.v1 --args service=esm-infra` with sudo + Then API data field output is: + """ + { + "attributes": { + "disabled": [] + }, + "meta": { + "environment_vars": [] + }, + "type": "DisableService" + } + """ + # disables dependent services + When I run `pro enable ros-updates --assume-yes` with sudo + When I run `pro api u.pro.services.disable.v1 --args service=esm-apps` with sudo + Then API data field output is: + """ + { + "attributes": { + "disabled": [ + "esm-apps", + "ros", + "ros-updates" + ] + }, + "meta": { + "environment_vars": [] + }, + "type": "DisableService" + } + """ + # purge works and post enable messages work + When I apt install `curl` + When I run `apt-cache policy curl` as non-root + Then stdout matches regexp: + """ + \*\*\* \+esm.* 510 + """ + When I run `pro api u.pro.services.disable.v1 --data '{"service": "esm-infra", "purge": true}'` with sudo + Then API data field output is: + """ + { + "attributes": { + "disabled": [ + "esm-infra" + ] + }, + "meta": { + "environment_vars": [] + }, + "type": "DisableService" + } + """ + When I run `apt-cache policy curl` as non-root + Then stdout contains substring: + """ + *** 500 + """ + + Examples: + | release | machine_type | curl_version | + | xenial | lxd-container | 7.47.0-1ubuntu2.19 | + | bionic | lxd-container | 7.58.0-2ubuntu3.24 | + + Scenario Outline: u.pro.services.disable.v1 vm services + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt update + And I attach `contract_token` with sudo + # Basic disable + And I run `pro api u.pro.services.disable.v1 --args service=livepatch` with sudo + Then API data field output is: + """ + { + "attributes": { + "disabled": [ + "livepatch" + ] + }, + "meta": { + "environment_vars": [] + }, + "type": "DisableService" + } + """ + # fails when purge not supported + When I run `pro enable realtime-kernel --access-only` with sudo + When I verify that running `pro api u.pro.services.disable.v1 --data '{"service": "realtime-kernel", "purge": true}'` `with sudo` exits `1` + Then API errors field output is: + """ + [ + { + "code": "entitlement-not-disabled", + "meta": { + "reason": { + "additional_info": null, + "code": "disable-purge-not-supported", + "title": "Real-time kernel does not support being disabled with --purge" + } + }, + "title": "failed to disable realtime-kernel" + } + ] + """ + + Examples: + | release | machine_type | + | jammy | lxd-vm | + + Scenario Outline: u.pro.services.disable.v1 with progress + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `apt-get update` with sudo + And I attach `contract_token` with sudo + # Basic disable + And I run shell command `pro api u.pro.services.disable.v1 --show-progress --args service=esm-infra` with sudo + Then stdout contains substring: + """ + {"total_steps": 2, "done_steps": 0, "previous_step_message": null, "current_step_message": "Removing APT access to Ubuntu Pro: ESM Infra"} + {"total_steps": 2, "done_steps": 1, "previous_step_message": "Removing APT access to Ubuntu Pro: ESM Infra", "current_step_message": "Updating package lists"} + {"total_steps": 2, "done_steps": 2, "previous_step_message": "Updating package lists", "current_step_message": null} + {"_schema_version": "v1", "data": {"attributes": {"disabled": ["esm-infra"]}, "meta": {"environment_vars": []}, "type": "DisableService"}, "errors": [], "result": "success" + """ + # Disabling multiple services shows steps correctly + When I run `pro enable ros-updates --assume-yes` with sudo + When I run `pro api u.pro.services.disable.v1 --show-progress --args service=esm-apps` with sudo + Then stdout contains substring: + """ + {"total_steps": 6, "done_steps": 0, "previous_step_message": null, "current_step_message": "Removing APT access to ROS ESM All Updates"} + {"total_steps": 6, "done_steps": 1, "previous_step_message": "Removing APT access to ROS ESM All Updates", "current_step_message": "Updating package lists"} + {"total_steps": 6, "done_steps": 2, "previous_step_message": "Updating package lists", "current_step_message": "Removing APT access to ROS ESM Security Updates"} + {"total_steps": 6, "done_steps": 3, "previous_step_message": "Removing APT access to ROS ESM Security Updates", "current_step_message": "Updating package lists"} + {"total_steps": 6, "done_steps": 4, "previous_step_message": "Updating package lists", "current_step_message": "Removing APT access to Ubuntu Pro: ESM Apps"} + {"total_steps": 6, "done_steps": 5, "previous_step_message": "Removing APT access to Ubuntu Pro: ESM Apps", "current_step_message": "Updating package lists"} + {"total_steps": 6, "done_steps": 6, "previous_step_message": "Updating package lists", "current_step_message": null} + {"_schema_version": "v1", "data": {"attributes": {"disabled": ["esm-apps", "ros", "ros-updates"]}, "meta": {"environment_vars": []}, "type": "DisableService"}, "errors": [], "result": "success" + """ + + Examples: + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/api_enable.feature ubuntu-advantage-tools-32~16.04/features/api_enable.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/api_enable.feature 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/api_enable.feature 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,297 @@ +Feature: u.pro.services.enable + + Scenario Outline: u.pro.services.enable.v1 container services + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt update + # Requires attach + When I verify that running `pro api u.pro.services.enable.v1 --args service=esm-infra` `with sudo` exits `1` + Then API errors field output is: + """ + [ + { + "code": "unattached", + "meta": {}, + "title": "This machine is not attached to an Ubuntu Pro subscription.\nSee https://ubuntu.com/pro" + } + ] + """ + When I attach `contract_token` with sudo and options `--no-auto-enable` + # Requires root + When I verify that running `pro api u.pro.services.enable.v1 --args service=esm-infra` `as non-root` exits `1` + Then API errors field output is: + """ + [ + { + "code": "nonroot-user", + "meta": {}, + "title": "This command must be run as root (try using sudo)." + } + ] + """ + # Invalid service name + When I verify that running `pro api u.pro.services.enable.v1 --args service=invalid` `with sudo` exits `1` + Then API errors field output is: + """ + [ + { + "code": "entitlement-not-found", + "meta": { + "entitlement_name": "invalid" + }, + "title": "could not find entitlement named \"invalid\"" + } + ] + """ + # Basic enable + When I run `pro api u.pro.services.enable.v1 --args service=esm-infra` with sudo + Then API data field output is: + """ + { + "attributes": { + "disabled": [], + "enabled": [ + "esm-infra" + ], + "messages": [], + "reboot_required": false + }, + "meta": { + "environment_vars": [] + }, + "type": "EnableService" + } + """ + Then I verify that `esm-infra` is enabled + # Enable already enabled service succeeds + When I run `pro api u.pro.services.enable.v1 --args service=esm-infra` with sudo + Then API data field output is: + """ + { + "attributes": { + "disabled": [], + "enabled": [], + "messages": [], + "reboot_required": false + }, + "meta": { + "environment_vars": [] + }, + "type": "EnableService" + } + """ + # enables required services + When I run `pro api u.pro.services.enable.v1 --args service=ros` with sudo + Then API data field output is: + """ + { + "attributes": { + "disabled": [], + "enabled": [ + "esm-apps", + "ros" + ], + "messages": [], + "reboot_required": false + }, + "meta": { + "environment_vars": [] + }, + "type": "EnableService" + } + """ + # Access only works and post enable messages work + When I run `pro api u.pro.services.enable.v1 --data '{"service": "cis", "access_only": true}'` with sudo + Then API data field output is: + """ + { + "attributes": { + "disabled": [], + "enabled": [ + "cis" + ], + "messages": [ + "Visit https://ubuntu.com/security/cis to learn how to use CIS" + ], + "reboot_required": false + }, + "meta": { + "environment_vars": [] + }, + "type": "EnableService" + } + """ + When I run `apt-cache policy usg-common` as non-root + Then stdout contains substring: + """ + Installed: (none) + """ + # Access only on service that doesn't support it fails + When I verify that running `pro api u.pro.services.enable.v1 --data '{"service": "ros-updates", "access_only": true}'` `with sudo` exits `1` + Then API errors field output is: + """ + [ + { + "code": "entitlement-not-enabled", + "meta": { + "reason": { + "additional_info": null, + "code": "enable-access-only-not-supported", + "title": "ROS ESM All Updates does not support being enabled with --access-only" + } + }, + "title": "failed to enable ros-updates" + } + ] + """ + + Examples: + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + + Scenario Outline: u.pro.services.enable.v1 landscape + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt update + And I attach `contract_token` with sudo and options `--no-auto-enable` + When I verify that running `pro api u.pro.services.enable.v1 --args service=landscape` `with sudo` exits `1` + Then API errors field output is: + """ + [ + { + "code": "not-supported", + "meta": {}, + "title": "The operation is not supported" + } + ] + """ + + Examples: + | release | machine_type | + | mantic | lxd-container | + + Scenario Outline: u.pro.services.enable.v1 vm services + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt update + And I attach `contract_token` with sudo and options `--no-auto-enable` + # Basic enable + And I run `pro api u.pro.services.enable.v1 --args service=livepatch` with sudo + Then API data field output is: + """ + { + "attributes": { + "disabled": [], + "enabled": [ + "livepatch" + ], + "messages": [], + "reboot_required": false + }, + "meta": { + "environment_vars": [] + }, + "type": "EnableService" + } + """ + # disables incompatible services and variant works + When I run `pro api u.pro.services.enable.v1 --data '{"service": "realtime-kernel", "variant": "intel-iotg"}'` with sudo + Then API data field output matches regexp: + """ + { + "attributes": { + "disabled": [ + "livepatch" + ], + "enabled": [ + "realtime-kernel" + ], + "messages": [], + "reboot_required": true + }, + "meta": { + "environment_vars": [] + }, + "type": "EnableService" + } + """ + When I run `pro api u.pro.status.enabled_services.v1` with sudo + Then API data field output matches regexp: + """ + \s*{ + \s* "name": "realtime-kernel", + \s* "variant_enabled": true, + \s* "variant_name": "intel-iotg" + \s*} + """ + + Examples: + | release | machine_type | + | jammy | lxd-vm | + + Scenario Outline: u.pro.services.enable.v1 with progress + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `apt-get update` with sudo + And I attach `contract_token` with sudo and options `--no-auto-enable` + # Basic enable + And I run shell command `pro api u.pro.services.enable.v1 --show-progress --args service=esm-infra` with sudo + Then stdout contains substring: + """ + {"total_steps": 2, "done_steps": 0, "previous_step_message": null, "current_step_message": "Configuring APT access to Ubuntu Pro: ESM Infra"} + {"total_steps": 2, "done_steps": 1, "previous_step_message": "Configuring APT access to Ubuntu Pro: ESM Infra", "current_step_message": "Updating Ubuntu Pro: ESM Infra package lists"} + {"total_steps": 2, "done_steps": 2, "previous_step_message": "Updating Ubuntu Pro: ESM Infra package lists", "current_step_message": null} + {"_schema_version": "v1", "data": {"attributes": {"disabled": [], "enabled": ["esm-infra"], "messages": [], "reboot_required": false}, "meta": {"environment_vars": []}, "type": "EnableService"}, "errors": [], "result": "success" + """ + # Enabling multiple services shows steps correctly + When I run shell command `pro api u.pro.services.enable.v1 --show-progress --args service=ros-updates` with sudo + Then stdout contains substring: + """ + {"total_steps": 6, "done_steps": 0, "previous_step_message": null, "current_step_message": "Configuring APT access to Ubuntu Pro: ESM Apps"} + {"total_steps": 6, "done_steps": 1, "previous_step_message": "Configuring APT access to Ubuntu Pro: ESM Apps", "current_step_message": "Updating Ubuntu Pro: ESM Apps package lists"} + {"total_steps": 6, "done_steps": 2, "previous_step_message": "Updating Ubuntu Pro: ESM Apps package lists", "current_step_message": "Configuring APT access to ROS ESM Security Updates"} + {"total_steps": 6, "done_steps": 3, "previous_step_message": "Configuring APT access to ROS ESM Security Updates", "current_step_message": "Updating ROS ESM Security Updates package lists"} + {"total_steps": 6, "done_steps": 4, "previous_step_message": "Updating ROS ESM Security Updates package lists", "current_step_message": "Configuring APT access to ROS ESM All Updates"} + {"total_steps": 6, "done_steps": 5, "previous_step_message": "Configuring APT access to ROS ESM All Updates", "current_step_message": "Updating ROS ESM All Updates package lists"} + {"total_steps": 6, "done_steps": 6, "previous_step_message": "Updating ROS ESM All Updates package lists", "current_step_message": null} + {"_schema_version": "v1", "data": {"attributes": {"disabled": [], "enabled": ["esm-apps", "ros", "ros-updates"], "messages": [], "reboot_required": false}, "meta": {"environment_vars": []}, "type": "EnableService"}, "errors": [], "result": "success" + """ + + Examples: + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + + Scenario Outline: u.pro.services.enable.v1 vm services with progress + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt update + And I attach `contract_token` with sudo and options `--no-auto-enable` + And I run `pro api u.pro.services.enable.v1 --args service=livepatch --show-progress` with sudo + Then stdout contains substring: + """ + {"total_steps": 2, "done_steps": 0, "previous_step_message": null, "current_step_message": "Installing Livepatch"} + {"total_steps": 2, "done_steps": 1, "previous_step_message": "Installing Livepatch", "current_step_message": "Setting up Livepatch"} + {"total_steps": 2, "done_steps": 2, "previous_step_message": "Setting up Livepatch", "current_step_message": null} + {"_schema_version": "v1", "data": {"attributes": {"disabled": [], "enabled": ["livepatch"], "messages": [], "reboot_required": false}, "meta": {"environment_vars": []}, "type": "EnableService"}, "errors": [], "result": "success" + """ + # disables incompatible services and variant works + When I run `pro api u.pro.services.enable.v1 --show-progress --data '{"service": "realtime-kernel", "variant": "intel-iotg"}'` with sudo + Then stdout contains substring: + """ + {"total_steps": 4, "done_steps": 0, "previous_step_message": null, "current_step_message": "Executing `/snap/bin/canonical-livepatch disable`"} + {"total_steps": 4, "done_steps": 1, "previous_step_message": "Executing `/snap/bin/canonical-livepatch disable`", "current_step_message": "Configuring APT access to Real-time Intel IOTG Kernel"} + {"total_steps": 4, "done_steps": 2, "previous_step_message": "Configuring APT access to Real-time Intel IOTG Kernel", "current_step_message": "Updating Real-time Intel IOTG Kernel package lists"} + {"total_steps": 4, "done_steps": 3, "previous_step_message": "Updating Real-time Intel IOTG Kernel package lists", "current_step_message": "Installing Real-time Intel IOTG Kernel packages"} + {"total_steps": 4, "done_steps": 4, "previous_step_message": "Installing Real-time Intel IOTG Kernel packages", "current_step_message": null} + {"_schema_version": "v1", "data": {"attributes": {"disabled": ["livepatch"], "enabled": ["realtime-kernel"], "messages": [], "reboot_required": true}, "meta": {"environment_vars": []}, "type": "EnableService"}, "errors": [], "result": "success" + """ + When I run `pro api u.pro.status.enabled_services.v1` with sudo + Then API data field output matches regexp: + """ + \s*{ + \s* "name": "realtime-kernel", + \s* "variant_enabled": true, + \s* "variant_name": "intel-iotg" + \s*} + """ + + Examples: + | release | machine_type | + | jammy | lxd-vm | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/api_fix_execute.feature ubuntu-advantage-tools-32~16.04/features/api_fix_execute.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/api_fix_execute.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/api_fix_execute.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,263 +1,1534 @@ Feature: Fix execute API endpoints - Scenario Outline: Fix execute command on invalid CVEs/USNs - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-1800-123456"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"description": null, "errors": \[{"error_type": "security-fix-not-found-issue", "failed_upgrades": null, "reason": "Error: CVE-1800-123456 not found."}\], "status": "error", "title": "CVE-1800-123456", "upgraded_packages": \[\]}\], "status": "error"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-123455"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "error", "usns": \[{"related_usns": \[\], "target_usn": {"description": null, "errors": \[{"error_type": "invalid-security-issue", "failed_upgrades": null, "reason": "Error: issue \\"USN-123455\\" is not recognized.\\n\\nCVEs should follow the pattern CVE-yyyy-nnn.\\n\\nUSNs should follow the pattern USN-nnnn."}\], "status": "error", "title": "USN-123455", "upgraded_packages": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-123455", "CVE-12"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"description": null, "errors": \[{"error_type": "invalid-security-issue", "failed_upgrades": null, "reason": "Error: issue \\"CVE-123455\\" is not recognized.\\n\\nCVEs should follow the pattern CVE-yyyy-nnn.\\n\\nUSNs should follow the pattern USN-nnnn."}\], "status": "error", "title": "CVE-123455", "upgraded_packages": \[\]}, {"description": null, "errors": \[{"error_type": "invalid-security-issue", "failed_upgrades": null, "reason": "Error: issue \\"CVE-12\\" is not recognized.\\n\\nCVEs should follow the pattern CVE-yyyy-nnn.\\n\\nUSNs should follow the pattern USN-nnnn."}\], "status": "error", "title": "CVE-12", "upgraded_packages": \[\]}\], "status": "error"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-123455", "USN-12"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "error", "usns": \[{"related_usns": \[\], "target_usn": {"description": null, "errors": \[{"error_type": "invalid-security-issue", "failed_upgrades": null, "reason": "Error: issue \\"USN-123455\\" is not recognized.\\n\\nCVEs should follow the pattern CVE-yyyy-nnn.\\n\\nUSNs should follow the pattern USN-nnnn."}\], "status": "error", "title": "USN-123455", "upgraded_packages": \[\]}}, {"related_usns": \[\], "target_usn": {"description": null, "errors": \[{"error_type": "invalid-security-issue", "failed_upgrades": null, "reason": "Error: issue \\"USN-12\\" is not recognized.\\n\\nCVEs should follow the pattern CVE-yyyy-nnn.\\n\\nUSNs should follow the pattern USN-nnnn."}\], "status": "error", "title": "USN-12", "upgraded_packages": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ + Scenario Outline: Fix execute command on invalid CVEs/USNs + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-1800-123456"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_execute` schema + Then API full output matches regexp: + """ + { + "_schema_version": "v1", + "data": { + "attributes": { + "cves_data": { + "cves": [ + { + "description": null, + "errors": [ + { + "error_type": "security-fix-not-found-issue", + "failed_upgrades": null, + "reason": "Error: CVE-1800-123456 not found." + } + ], + "status": "error", + "title": "CVE-1800-123456", + "upgraded_packages": [] + } + ], + "status": "error" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixExecute" + }, + "errors": [], + "result": "success", + "version": ".*", + "warnings": [] + } + """ + When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-123455"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_execute` schema + Then API full output matches regexp: + """ + { + "_schema_version": "v1", + "data": { + "attributes": { + "usns_data": { + "status": "error", + "usns": [ + { + "related_usns": [], + "target_usn": { + "description": null, + "errors": [ + { + "error_type": "invalid-security-issue", + "failed_upgrades": null, + "reason": "Error: issue \\"USN-123455\\" is not recognized.\n\nCVEs should follow the pattern CVE-yyyy-nnn.\n\nUSNs should follow the pattern USN-nnnn." + } + ], + "status": "error", + "title": "USN-123455", + "upgraded_packages": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + }, + "errors": [], + "result": "success", + "version": ".*", + "warnings": [] + } + """ + When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-123455", "CVE-12"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_execute` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "description": null, + "errors": [ + { + "error_type": "invalid-security-issue", + "failed_upgrades": null, + "reason": "Error: issue \\"CVE-123455\\" is not recognized.\n\nCVEs should follow the pattern CVE-yyyy-nnn.\n\nUSNs should follow the pattern USN-nnnn." + } + ], + "status": "error", + "title": "CVE-123455", + "upgraded_packages": [] + }, + { + "description": null, + "errors": [ + { + "error_type": "invalid-security-issue", + "failed_upgrades": null, + "reason": "Error: issue \\"CVE-12\\" is not recognized.\n\nCVEs should follow the pattern CVE-yyyy-nnn.\n\nUSNs should follow the pattern USN-nnnn." + } + ], + "status": "error", + "title": "CVE-12", + "upgraded_packages": [] + } + ], + "status": "error" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixExecute" + } + """ + When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-123455", "USN-12"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_execute` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "error", + "usns": [ + { + "related_usns": [], + "target_usn": { + "description": null, + "errors": [ + { + "error_type": "invalid-security-issue", + "failed_upgrades": null, + "reason": "Error: issue \\"USN-123455\\" is not recognized.\n\nCVEs should follow the pattern CVE-yyyy-nnn.\n\nUSNs should follow the pattern USN-nnnn." + } + ], + "status": "error", + "title": "USN-123455", + "upgraded_packages": [] + } + }, + { + "related_usns": [], + "target_usn": { + "description": null, + "errors": [ + { + "error_type": "invalid-security-issue", + "failed_upgrades": null, + "reason": "Error: issue \\"USN-12\\" is not recognized.\n\nCVEs should follow the pattern CVE-yyyy-nnn.\n\nUSNs should follow the pattern USN-nnnn." + } + ], + "status": "error", + "title": "USN-12", + "upgraded_packages": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ - Examples: ubuntu release details - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | + Examples: ubuntu release details + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | - Scenario Outline: Fix execute on a Focal machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2020-28196"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"description": "Kerberos vulnerability", "errors": null, "status": "fixed", "title": "CVE-2020-28196", "upgraded_packages": \[\]}\], "status": "fixed"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2022-24959"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"description": "Linux kernel vulnerabilities", "errors": null, "status": "not-affected", "title": "CVE-2022-24959", "upgraded_packages": \[\]}\], "status": "not-affected"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2020-28196", "CVE-2022-24959"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"description": "Kerberos vulnerability", "errors": null, "status": "fixed", "title": "CVE-2020-28196", "upgraded_packages": \[\]}, {"description": "Linux kernel vulnerabilities", "errors": null, "status": "not-affected", "title": "CVE-2022-24959", "upgraded_packages": \[\]}\], "status": "fixed"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I apt update - And I apt install `libawl-php=0.60-1` - And I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4539-1"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "error", "usns": \[{"related_usns": \[\], "target_usn": {"description": "AWL vulnerability", "errors": \[{"error_type": "fix-require-root", "failed_upgrades": \[{"name": "awl", "pocket": "standard-updates"}\], "reason": "Package fixes cannot be installed.\\nTo install them, run this command as root \(try using sudo\)"}\], "status": "error", "title": "USN-4539-1", "upgraded_packages": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\] - """ - When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4539-1"]}'` with sudo - Then stdout is a json matching the `api_response` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "fixed", "usns": \[{"related_usns": \[\], "target_usn": {"description": "AWL vulnerability", "errors": null, "status": "fixed", "title": "USN-4539-1", "upgraded_packages": \[{"name": "libawl-php", "pocket": "standard-updates", "version": ".*"}\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4539-1"]}'` with sudo - Then stdout is a json matching the `api_response` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "fixed", "usns": \[{"related_usns": \[\], "target_usn": {"description": "AWL vulnerability", "errors": null, "status": "fixed", "title": "USN-4539-1", "upgraded_packages": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I apt install `rsync=3.1.3-8 zlib1g=1:1.2.11.dfsg-2ubuntu1` - And I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-5573-1"]}'` with sudo - Then stdout is a json matching the `api_response` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "fixed", "usns": \[{"related_usns": \[{"description": "zlib vulnerability", "errors": null, "status": "not-affected", "title": "USN-5570-1", "upgraded_packages": \[\]}, {"description": "zlib vulnerability", "errors": null, "status": "fixed", "title": "USN-5570-2", "upgraded_packages": \[{"name": "zlib1g", "pocket": "standard-updates", "version": ".*"}\]}\], "target_usn": {"description": "rsync vulnerability", "errors": null, "status": "fixed", "title": "USN-5573-1", "upgraded_packages": \[{"name": "rsync", "pocket": "standard-updates", "version": ".*"}\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4539-1", "USN-5573-1"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "fixed", "usns": \[{"related_usns": \[\], "target_usn": {"description": "AWL vulnerability", "errors": null, "status": "fixed", "title": "USN-4539-1", "upgraded_packages": \[\]}}, {"related_usns": \[{"description": "zlib vulnerability", "errors": null, "status": "not-affected", "title": "USN-5570-1", "upgraded_packages": \[\]}, {"description": "zlib vulnerability", "errors": null, "status": "fixed", "title": "USN-5570-2", "upgraded_packages": \[\]}\], "target_usn": {"description": "rsync vulnerability", "errors": null, "status": "fixed", "title": "USN-5573-1", "upgraded_packages": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ + Scenario Outline: Fix execute on a Focal machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2020-28196"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_execute` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "description": "Kerberos vulnerability", + "errors": null, + "status": "fixed", + "title": "CVE-2020-28196", + "upgraded_packages": [] + } + ], + "status": "fixed" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixExecute" + } + """ + When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2022-24959"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_execute` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "description": "Linux kernel vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "CVE-2022-24959", + "upgraded_packages": [] + } + ], + "status": "not-affected" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixExecute" + } + """ + When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2020-28196", "CVE-2022-24959"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_execute` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "description": "Kerberos vulnerability", + "errors": null, + "status": "fixed", + "title": "CVE-2020-28196", + "upgraded_packages": [] + }, + { + "description": "Linux kernel vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "CVE-2022-24959", + "upgraded_packages": [] + } + ], + "status": "fixed" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixExecute" + } + """ + When I apt install `libawl-php=0.60-1` + And I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4539-1"]}'` as non-root + Then stdout is a json matching the `api_response` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "error", + "usns": [ + { + "related_usns": [], + "target_usn": { + "description": "AWL vulnerability", + "errors": [ + { + "error_type": "fix-require-root", + "failed_upgrades": [ + { + "name": "awl", + "pocket": "standard-updates" + } + ], + "reason": "Package fixes cannot be installed.\nTo install them, run this command as root (try using sudo)" + } + ], + "status": "error", + "title": "USN-4539-1", + "upgraded_packages": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ + When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4539-1"]}'` with sudo + Then stdout is a json matching the `api_response` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "fixed", + "usns": [ + { + "related_usns": [], + "target_usn": { + "description": "AWL vulnerability", + "errors": null, + "status": "fixed", + "title": "USN-4539-1", + "upgraded_packages": [ + { + "name": "libawl-php", + "pocket": "standard-updates", + "version": ".*" + } + ] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ + When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4539-1"]}'` with sudo + Then stdout is a json matching the `api_response` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "fixed", + "usns": [ + { + "related_usns": [], + "target_usn": { + "description": "AWL vulnerability", + "errors": null, + "status": "fixed", + "title": "USN-4539-1", + "upgraded_packages": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ + When I apt install `rsync=3.1.3-8 zlib1g=1:1.2.11.dfsg-2ubuntu1` + And I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-5573-1"]}'` with sudo + Then stdout is a json matching the `api_response` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "fixed", + "usns": [ + { + "related_usns": [ + { + "description": "zlib vulnerability", + "errors": null, + "status": "not-affected", + "title": "USN-5570-1", + "upgraded_packages": [] + }, + { + "description": "zlib vulnerability", + "errors": null, + "status": "fixed", + "title": "USN-5570-2", + "upgraded_packages": [ + { + "name": "zlib1g", + "pocket": "standard-updates", + "version": ".*" + } + ] + }, + { + "description": "klibc vulnerabilities", + "errors": null, + "status": "fixed", + "title": "USN-6736-1", + "upgraded_packages": [] + } + ], + "target_usn": { + "description": "rsync vulnerability", + "errors": null, + "status": "fixed", + "title": "USN-5573-1", + "upgraded_packages": [ + { + "name": "rsync", + "pocket": "standard-updates", + "version": ".*" + } + ] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ + When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4539-1", "USN-5573-1"]}'` as non-root + Then stdout is a json matching the `api_response` schema + Then API data field output matches regexp: + """ + "attributes": { + "usns_data": { + "status": "fixed", + "usns": [ + { + "related_usns": [], + "target_usn": { + "description": "AWL vulnerability", + "errors": null, + "status": "fixed", + "title": "USN-4539-1", + "upgraded_packages": [] + } + }, + { + "related_usns": [ + { + "description": "zlib vulnerability", + "errors": null, + "status": "not-affected", + "title": "USN-5570-1", + "upgraded_packages": [] + }, + { + "description": "zlib vulnerability", + "errors": null, + "status": "fixed", + "title": "USN-5570-2", + "upgraded_packages": [] + }, + { + "description": "klibc vulnerabilities", + "errors": null, + "status": "fixed", + "title": "USN-6736-1", + "upgraded_packages": [] + } + ], + "target_usn": { + "description": "rsync vulnerability", + "errors": null, + "status": "fixed", + "title": "USN-5573-1", + "upgraded_packages": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ - Examples: ubuntu release details - | release | machine_type | - | focal | lxd-container | + Examples: ubuntu release details + | release | machine_type | + | focal | lxd-container | - Scenario Outline: Fix execute API command on a Xenial machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2020-15180"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"description": ".*", "errors": null, "status": "not-affected", "title": "CVE-2020-15180", "upgraded_packages": \[\]}\], "status": "not-affected"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2020-28196"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"description": ".*", "errors": null, "status": "fixed", "title": "CVE-2020-28196", "upgraded_packages": \[\]}\], "status": "fixed"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I apt update - And I apt install `expat=2.1.0-7 swish-e matanza ghostscript` - And I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2017-9233"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"description": ".*", "errors": \[{"error_type": "security-issue-not-fixed", "failed_upgrades": \[{"name": "matanza", "pocket": null}, {"name": "swish-e", "pocket": null}\], "reason": "Ubuntu security engineers are investigating this issue."}, {"error_type": "fix-require-root", "failed_upgrades": \[{"name": "expat", "pocket": "standard-updates"}\], "reason": "Package fixes cannot be installed.\\nTo install them, run this command as root \(try using sudo\)"}\], "status": "error", "title": "CVE-2017-9233", "upgraded_packages": \[\]}\], "status": "error"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2017-9233"]}'` with sudo - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"description": ".*", "errors": \[{"error_type": "security-issue-not-fixed", "failed_upgrades": \[{"name": "matanza", "pocket": null}, {"name": "swish-e", "pocket": null}\], "reason": "Ubuntu security engineers are investigating this issue."}\], "status": "still-affected", "title": "CVE-2017-9233", "upgraded_packages": \[{"name": "expat", "pocket": "standard-updates", "version": ".*"}\]}\], "status": "still-affected"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-28196", "CVE-2020-15180", "CVE-2017-9233"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"additional_data": {}, "affected_packages": \["krb5"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"pocket": "standard-updates", "source_packages": \["krb5"\], "status": "cve-already-fixed"}, "operation": "no-op", "order": 1}\], "title": "CVE-2020-28196", "warnings": \[\]}, {"additional_data": {}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "CVE-2020-15180", "warnings": \[\]}, {"additional_data": {}, "affected_packages": \["expat", "matanza", "swish-e"\], "description": ".*", "error": null, "expected_status": "still-affected", "plan": \[{"data": {"pocket": "standard-updates", "source_packages": \["expat"\], "status": "cve-already-fixed"}, "operation": "no-op", "order": 2}\], "title": "CVE-2017-9233", "warnings": \[{"data": {"source_packages": \["matanza", "swish-e"\], "status": "needs-triage"}, "order": 1, "warning_type": "security-issue-not-fixed"}\]}\], "expected_status": "still-affected"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\] - """ - When I apt install `libawl-php` - And I reboot the machine - And I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4539-1"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "not-affected", "usns": \[{"related_usns": \[\], "target_usn": {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-4539-1", "upgraded_packages": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-5079-2"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "still-affected", "usns": \[{"related_usns": null, "target_usn": {"description": ".*", "errors": \[{"error_type": "fix-requires-attach", "failed_upgrades": \[{"name": "curl", "pocket": "esm-infra"}\], "reason": "The update is not installed because this system is not attached to a\\nsubscription.\\n"}\], "status": "still-affected", "title": "USN-5079-2", "upgraded_packages": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I attach `contract_token` with sudo - And I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-5079-2"]}'` with sudo - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "fixed", "usns": \[{"related_usns": \[{"description": ".*", "errors": null, "status": "not-affected", "title": "USN-5079-1", "upgraded_packages": \[\]}\], "target_usn": {"description": "curl vulnerabilities", "errors": null, "status": "fixed", "title": "USN-5079-2", "upgraded_packages": \[{"name": "curl", "pocket": "esm-infra", "version": ".*"}, {"name": "libcurl3-gnutls", "pocket": "esm-infra", "version": ".*"}\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-5051-2"]}'` with sudo - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "fixed", "usns": \[{"related_usns": \[{"description": ".*", "errors": null, "status": "not-affected", "title": "USN-5051-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-5051-3", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-5088-1", "upgraded_packages": \[\]}\], "target_usn": {"description": ".*", "errors": null, "status": "fixed", "title": "USN-5051-2", "upgraded_packages": \[{"name": "libssl1.0.0", "pocket": "esm-infra", "version": ".*"}, {"name": "openssl", "pocket": "esm-infra", "version": ".*"}\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-5378-4"]}'` with sudo - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "fixed", "usns": \[{"related_usns": \[{"description": ".*", "errors": null, "status": "not-affected", "title": "USN-5378-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-5378-2", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "fixed", "title": "USN-5378-3", "upgraded_packages": \[{"name": "liblzma5", "pocket": "esm-infra", "version": ".*"}, {"name": "xz-utils", "pocket": "esm-infra", "version": ".*"}\]}\], "target_usn": {"description": ".*", "errors": null, "status": "fixed", "title": "USN-5378-4", "upgraded_packages": \[{"name": "gzip", "pocket": "esm-infra", "version": ".*"}\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-5051-2", "USN-5378-4"]}'` with sudo - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "fixed", "usns": \[{"related_usns": \[{"description": ".*", "errors": null, "status": "not-affected", "title": "USN-5051-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-5051-3", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-5088-1", "upgraded_packages": \[\]}\], "target_usn": {"description": ".*", "errors": null, "status": "fixed", "title": "USN-5051-2", "upgraded_packages": \[\]}}, {"related_usns": \[{"description": ".*", "errors": null, "status": "not-affected", "title": "USN-5378-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-5378-2", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "fixed", "title": "USN-5378-3", "upgraded_packages": \[\]}\], "target_usn": {"description": ".*", "errors": null, "status": "fixed", "title": "USN-5378-4", "upgraded_packages": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro detach --assume-yes` with sudo - And I run `sed -i "/xenial-updates/d" /etc/apt/sources.list` with sudo - And I run `sed -i "/xenial-security/d" /etc/apt/sources.list` with sudo - And I apt update - And I apt install `squid` - And I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2020-25097"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"description": ".*", "errors": \[{"error_type": "package-cannot-be-installed", "failed_upgrades": \[{"name": "squid", "pocket": "standard-updates"}\], "reason": "Cannot install package squid version .*"}, {"error_type": "package-cannot-be-installed", "failed_upgrades": \[{"name": "squid-common", "pocket": "standard-updates"}\], "reason": "Cannot install package squid-common version .*"}\], "status": "still-affected", "title": "CVE-2020-25097", "upgraded_packages": \[\]}\], "status": "still-affected"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ + Scenario Outline: Fix execute API command on a Xenial machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2020-15180"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_execute` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "description": "MariaDB vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "CVE-2020-15180", + "upgraded_packages": [] + } + ], + "status": "not-affected" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixExecute" + } + """ + When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2020-28196"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_execute` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "description": "Kerberos vulnerability", + "errors": null, + "status": "fixed", + "title": "CVE-2020-28196", + "upgraded_packages": [] + } + ], + "status": "fixed" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixExecute" + } + """ + When I apt install `expat=2.1.0-7 swish-e matanza ghostscript` + And I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2017-9233"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_execute` schema + And API data field output matches regexp: + """ + "attributes": { + "cves_data": { + "cves": [ + { + "description": "Expat vulnerability", + "errors": [ + { + "error_type": "security-issue-not-fixed", + "failed_upgrades": [ + { + "name": "matanza", + "pocket": null + }, + { + "name": "swish-e", + "pocket": null + } + ], + "reason": "Ubuntu security engineers are investigating this issue." + }, + { + "error_type": "fix-require-root", + "failed_upgrades": [ + { + "name": "expat", + "pocket": "standard-updates" + } + ], + "reason": "Package fixes cannot be installed.\nTo install them, run this command as root (try using sudo)" + } + ], + "status": "error", + "title": "CVE-2017-9233", + "upgraded_packages": [] + } + ], + "status": "error" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixExecute" + } + """ + When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2017-9233"]}'` with sudo + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_execute` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "description": "Expat vulnerability", + "errors": [ + { + "error_type": "security-issue-not-fixed", + "failed_upgrades": [ + { + "name": "matanza", + "pocket": null + }, + { + "name": "swish-e", + "pocket": null + } + ], + "reason": "Ubuntu security engineers are investigating this issue." + } + ], + "status": "still-affected", + "title": "CVE-2017-9233", + "upgraded_packages": [ + { + "name": "expat", + "pocket": "standard-updates", + "version": ".*" + } + ] + } + ], + "status": "still-affected" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixExecute" + } + """ + When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2020-28196", "CVE-2020-15180", "CVE-2017-9233"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_execute` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "description": "Kerberos vulnerability", + "errors": null, + "status": "fixed", + "title": "CVE-2020-28196", + "upgraded_packages": [] + }, + { + "description": "MariaDB vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "CVE-2020-15180", + "upgraded_packages": [] + }, + { + "description": "Expat vulnerability", + "errors": [ + { + "error_type": "security-issue-not-fixed", + "failed_upgrades": [ + { + "name": "matanza", + "pocket": null + }, + { + "name": "swish-e", + "pocket": null + } + ], + "reason": "Ubuntu security engineers are investigating this issue." + } + ], + "status": "still-affected", + "title": "CVE-2017-9233", + "upgraded_packages": [] + } + ], + "status": "still-affected" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixExecute" + } + """ + When I apt install `libawl-php` + And I reboot the machine + And I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4539-1"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_execute` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "not-affected", + "usns": [ + { + "related_usns": [], + "target_usn": { + "description": "AWL vulnerability", + "errors": null, + "status": "not-affected", + "title": "USN-4539-1", + "upgraded_packages": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ + When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-5079-2"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_execute` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "still-affected", + "usns": [ + { + "related_usns": null, + "target_usn": { + "description": "curl vulnerabilities", + "errors": [ + { + "error_type": "fix-requires-attach", + "failed_upgrades": [ + { + "name": "curl", + "pocket": "esm-infra" + } + ], + "reason": "The update is not installed because this system is not attached to a\nsubscription.\n" + } + ], + "status": "still-affected", + "title": "USN-5079-2", + "upgraded_packages": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ + When I attach `contract_token` with sudo + And I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-5079-2"]}'` with sudo + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_execute` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "fixed", + "usns": [ + { + "related_usns": [ + { + "description": "curl vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-5079-1", + "upgraded_packages": [] + } + ], + "target_usn": { + "description": "curl vulnerabilities", + "errors": null, + "status": "fixed", + "title": "USN-5079-2", + "upgraded_packages": [ + { + "name": "curl", + "pocket": "esm-infra", + "version": ".*" + }, + { + "name": "libcurl3-gnutls", + "pocket": "esm-infra", + "version": ".*" + } + ] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ + When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-5051-2"]}'` with sudo + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_execute` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "fixed", + "usns": [ + { + "related_usns": [ + { + "description": "OpenSSL vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-5051-1", + "upgraded_packages": [] + }, + { + "description": "OpenSSL vulnerability", + "errors": null, + "status": "not-affected", + "title": "USN-5051-3", + "upgraded_packages": [] + }, + { + "description": "EDK II vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-5088-1", + "upgraded_packages": [] + } + ], + "target_usn": { + "description": "OpenSSL vulnerability", + "errors": null, + "status": "fixed", + "title": "USN-5051-2", + "upgraded_packages": [ + { + "name": "libssl1.0.0", + "pocket": "esm-infra", + "version": ".*" + }, + { + "name": "openssl", + "pocket": "esm-infra", + "version": ".*" + } + ] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ + When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-5378-4"]}'` with sudo + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_execute` schema + And API data field output matches regexp: + """ + "attributes": { + "usns_data": { + "status": "fixed", + "usns": [ + { + "related_usns": [ + { + "description": "Gzip vulnerability", + "errors": null, + "status": "not-affected", + "title": "USN-5378-1", + "upgraded_packages": [] + }, + { + "description": "XZ Utils vulnerability", + "errors": null, + "status": "not-affected", + "title": "USN-5378-2", + "upgraded_packages": [] + }, + { + "description": "XZ Utils vulnerability", + "errors": null, + "status": "fixed", + "title": "USN-5378-3", + "upgraded_packages": [ + { + "name": "liblzma5", + "pocket": "esm-infra", + "version": ".*" + }, + { + "name": "xz-utils", + "pocket": "esm-infra", + "version": ".*" + } + ] + } + ], + "target_usn": { + "description": "Gzip vulnerability", + "errors": null, + "status": "fixed", + "title": "USN-5378-4", + "upgraded_packages": [ + { + "name": "gzip", + "pocket": "esm-infra", + "version": ".*" + } + ] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ + When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-5051-2", "USN-5378-4"]}'` with sudo + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_execute` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "fixed", + "usns": [ + { + "related_usns": [ + { + "description": "OpenSSL vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-5051-1", + "upgraded_packages": [] + }, + { + "description": "OpenSSL vulnerability", + "errors": null, + "status": "not-affected", + "title": "USN-5051-3", + "upgraded_packages": [] + }, + { + "description": "EDK II vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-5088-1", + "upgraded_packages": [] + } + ], + "target_usn": { + "description": "OpenSSL vulnerability", + "errors": null, + "status": "fixed", + "title": "USN-5051-2", + "upgraded_packages": [] + } + }, + { + "related_usns": [ + { + "description": "Gzip vulnerability", + "errors": null, + "status": "not-affected", + "title": "USN-5378-1", + "upgraded_packages": [] + }, + { + "description": "XZ Utils vulnerability", + "errors": null, + "status": "not-affected", + "title": "USN-5378-2", + "upgraded_packages": [] + }, + { + "description": "XZ Utils vulnerability", + "errors": null, + "status": "fixed", + "title": "USN-5378-3", + "upgraded_packages": [] + } + ], + "target_usn": { + "description": "Gzip vulnerability", + "errors": null, + "status": "fixed", + "title": "USN-5378-4", + "upgraded_packages": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ + When I run `pro detach --assume-yes` with sudo + And I run `sed -i "/xenial-updates/d" /etc/apt/sources.list` with sudo + And I run `sed -i "/xenial-security/d" /etc/apt/sources.list` with sudo + And I apt update + And I apt install `squid` + And I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2020-25097"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_execute` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "description": "Squid vulnerabilities", + "errors": [ + { + "error_type": "package-cannot-be-installed", + "failed_upgrades": [ + { + "name": "squid", + "pocket": "standard-updates" + } + ], + "reason": "Cannot install package squid version .*" + }, + { + "error_type": "package-cannot-be-installed", + "failed_upgrades": [ + { + "name": "squid-common", + "pocket": "standard-updates" + } + ], + "reason": "Cannot install package squid-common version .*" + } + ], + "status": "still-affected", + "title": "CVE-2020-25097", + "upgraded_packages": [] + } + ], + "status": "still-affected" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixExecute" + } + """ - Examples: ubuntu release details - | release | machine_type | - | xenial | lxd-container | + Examples: ubuntu release details + | release | machine_type | + | xenial | lxd-container | - Scenario Outline: Fix execute API command on a Bionic machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2020-28196"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"description": "Kerberos vulnerability", "errors": null, "status": "fixed", "title": "CVE-2020-28196", "upgraded_packages": \[\]}\], "status": "fixed"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I apt update - And I apt install `xterm=330-1ubuntu2` - And I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2021-27135"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"description": "xterm vulnerability", "errors": \[{"error_type": "fix-require-root", "failed_upgrades": \[{"name": "xterm", "pocket": "standard-updates"}\], "reason": "Package fixes cannot be installed.\\nTo install them, run this command as root \(try using sudo\)"}\], "status": "error", "title": "CVE-2021-27135", "upgraded_packages": \[\]}\], "status": "error"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2021-27135"]}'` with sudo - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_execute` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"description": "xterm vulnerability", "errors": null, "status": "fixed", "title": "CVE-2021-27135", "upgraded_packages": \[{"name": "xterm", "pocket": "standard-updates", "version": ".*"}\]}\], "status": "fixed"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I apt install `libawl-php` - And I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4539-1"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "not-affected", "usns": \[{"related_usns": \[\], "target_usn": {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-4539-1", "upgraded_packages": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I apt install `libbz2-1.0=1.0.6-8.1 bzip2=1.0.6-8.1` - And I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4038-3"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "error", "usns": \[{"related_usns": \[\], "target_usn": {"description": ".*", "errors": \[{"error_type": "fix-require-root", "failed_upgrades": \[{"name": "bzip2", "pocket": "standard-updates"}\], "reason": "Package fixes cannot be installed.\\nTo install them, run this command as root \(try using sudo\)"}\], "status": "error", "title": "USN-4038-3", "upgraded_packages": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4038-3"]}'` with sudo - Then stdout is a json matching the `api_response` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "fixed", "usns": \[{"related_usns": \[\], "target_usn": {"description": ".*", "errors": null, "status": "fixed", "title": "USN-4038-3", "upgraded_packages": \[{"name": "bzip2", "pocket": "standard-updates", "version": ".*"}, {"name": "libbz2-1.0", "pocket": "standard-updates", "version": ".*"}\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-6130-1"]}'` with sudo - Then stdout is a json matching the `api_response` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "not-affected", "usns": \[{"related_usns": \[{"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6033-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6122-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6123-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6124-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6127-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6131-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6132-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6135-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6149-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6150-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6162-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6173-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6175-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6186-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6222-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6256-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6385-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6460-1", "upgraded_packages": \[\]}\], "target_usn": {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-6130-1", "upgraded_packages": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4539-1", "USN-4038-1"]}'` with sudo - Then stdout is a json matching the `api_response` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"status": "fixed", "usns": \[{"related_usns": \[\], "target_usn": {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-4539-1", "upgraded_packages": \[\]}}, {"related_usns": \[{"description": "bzip2 vulnerabilities", "errors": null, "status": "not-affected", "title": "USN-4038-2", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-4146-1", "upgraded_packages": \[\]}, {"description": ".*", "errors": null, "status": "not-affected", "title": "USN-4146-2", "upgraded_packages": \[\]}\], "target_usn": {"description": ".*", "errors": null, "status": "fixed", "title": "USN-4038-1", "upgraded_packages": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixExecute"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ + Scenario Outline: Fix execute API command on a Bionic machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2020-28196"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_execute` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "description": "Kerberos vulnerability", + "errors": null, + "status": "fixed", + "title": "CVE-2020-28196", + "upgraded_packages": [] + } + ], + "status": "fixed" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixExecute" + } + """ + When I apt install `xterm=330-1ubuntu2` + And I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2021-27135"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_execute` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "description": "xterm vulnerability", + "errors": [ + { + "error_type": "fix-require-root", + "failed_upgrades": [ + { + "name": "xterm", + "pocket": "standard-updates" + } + ], + "reason": "Package fixes cannot be installed.\nTo install them, run this command as root (try using sudo)" + } + ], + "status": "error", + "title": "CVE-2021-27135", + "upgraded_packages": [] + } + ], + "status": "error" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixExecute" + } + """ + When I run `pro api u.pro.security.fix.cve.execute.v1 --data '{"cves": ["CVE-2021-27135"]}'` with sudo + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_execute` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "description": "xterm vulnerability", + "errors": null, + "status": "fixed", + "title": "CVE-2021-27135", + "upgraded_packages": [ + { + "name": "xterm", + "pocket": "standard-updates", + "version": ".*" + } + ] + } + ], + "status": "fixed" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixExecute" + } + """ + When I apt install `libawl-php` + And I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4539-1"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "not-affected", + "usns": [ + { + "related_usns": [], + "target_usn": { + "description": "AWL vulnerability", + "errors": null, + "status": "not-affected", + "title": "USN-4539-1", + "upgraded_packages": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ + When I apt install `libbz2-1.0=1.0.6-8.1 bzip2=1.0.6-8.1` + And I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4038-3"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "error", + "usns": [ + { + "related_usns": [], + "target_usn": { + "description": "bzip2 regression", + "errors": [ + { + "error_type": "fix-require-root", + "failed_upgrades": [ + { + "name": "bzip2", + "pocket": "standard-updates" + } + ], + "reason": "Package fixes cannot be installed.\nTo install them, run this command as root (try using sudo)" + } + ], + "status": "error", + "title": "USN-4038-3", + "upgraded_packages": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ + When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4038-3"]}'` with sudo + Then stdout is a json matching the `api_response` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "fixed", + "usns": [ + { + "related_usns": [], + "target_usn": { + "description": "bzip2 regression", + "errors": null, + "status": "fixed", + "title": "USN-4038-3", + "upgraded_packages": [ + { + "name": "bzip2", + "pocket": "standard-updates", + "version": ".*" + }, + { + "name": "libbz2-1.0", + "pocket": "standard-updates", + "version": ".*" + } + ] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ + When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-6130-1"]}'` with sudo + Then stdout is a json matching the `api_response` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "not-affected", + "usns": [ + { + "related_usns": [ + { + "description": "Linux kernel (OEM) vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6033-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel (OEM) vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6122-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel (OEM) vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6123-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel (OEM) vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6124-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6127-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6131-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6132-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel (Azure CVM) vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6135-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6149-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6150-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel (Intel IoTG) vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6162-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel (OEM) vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6173-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6175-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6186-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel (Xilinx ZynqMP) vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6222-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel (IoT) vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6256-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel (OEM) vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6385-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6460-1", + "upgraded_packages": [] + }, + { + "description": "Linux kernel vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6699-1", + "upgraded_packages": [] + } + ], + "target_usn": { + "description": "Linux kernel vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-6130-1", + "upgraded_packages": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ + When I run `pro api u.pro.security.fix.usn.execute.v1 --data '{"usns": ["USN-4539-1", "USN-4038-1"]}'` with sudo + Then stdout is a json matching the `api_response` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "status": "fixed", + "usns": [ + { + "related_usns": [], + "target_usn": { + "description": "AWL vulnerability", + "errors": null, + "status": "not-affected", + "title": "USN-4539-1", + "upgraded_packages": [] + } + }, + { + "related_usns": [ + { + "description": "bzip2 vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-4038-2", + "upgraded_packages": [] + }, + { + "description": "ClamAV vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-4146-1", + "upgraded_packages": [] + }, + { + "description": "ClamAV vulnerabilities", + "errors": null, + "status": "not-affected", + "title": "USN-4146-2", + "upgraded_packages": [] + } + ], + "target_usn": { + "description": "bzip2 vulnerabilities", + "errors": null, + "status": "fixed", + "title": "USN-4038-1", + "upgraded_packages": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixExecute" + } + """ - Examples: ubuntu release details - | release | machine_type | - | bionic | lxd-container | + Examples: ubuntu release details + | release | machine_type | + | bionic | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/api_fix_plan.feature ubuntu-advantage-tools-32~16.04/features/api_fix_plan.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/api_fix_plan.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/api_fix_plan.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,243 +1,2884 @@ Feature: Fix plan API endpoints - Scenario Outline: Fix command on an unattached machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-1800-123456"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"additional_data": {}, "affected_packages": null, "description": null, "error": {"code": "security-fix-not-found-issue", "msg": "Error: CVE-1800-123456 not found."}, "expected_status": "error", "plan": \[\], "title": "CVE-1800-123456", "warnings": \[\]}\], "expected_status": "error"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-123455"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"expected_status": "error", "usns": \[{"related_usns_plan": \[\], "target_usn_plan": {"additional_data": {}, "affected_packages": null, "description": null, "error": {"code": "invalid-security-issue", "msg": "Error: issue \\"USN-123455\\" is not recognized.\\n\\nCVEs should follow the pattern CVE-yyyy-nnn.\\n\\nUSNs should follow the pattern USN-nnnn."}, "expected_status": "error", "plan": \[\], "title": "USN-123455", "warnings": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-123455", "CVE-12"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_plan` schema - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"additional_data": {}, "affected_packages": null, "description": null, "error": {"code": "invalid-security-issue", "msg": "Error: issue \\"CVE-123455\\" is not recognized.\\n\\nCVEs should follow the pattern CVE-yyyy-nnn.\\n\\nUSNs should follow the pattern USN-nnnn."}, "expected_status": "error", "plan": \[\], "title": "CVE-123455", "warnings": \[\]}, {"additional_data": {}, "affected_packages": null, "description": null, "error": {"code": "invalid-security-issue", "msg": "Error: issue \\"CVE-12\\" is not recognized.\\n\\nCVEs should follow the pattern CVE-yyyy-nnn.\\n\\nUSNs should follow the pattern USN-nnnn."}, "expected_status": "error", "plan": \[\], "title": "CVE-12", "warnings": \[\]}\], "expected_status": "error"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-123455", "USN-12"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"expected_status": "error", "usns": \[{"related_usns_plan": \[\], "target_usn_plan": {"additional_data": {}, "affected_packages": null, "description": null, "error": {"code": "invalid-security-issue", "msg": "Error: issue \\"USN-123455\\" is not recognized.\\n\\nCVEs should follow the pattern CVE-yyyy-nnn.\\n\\nUSNs should follow the pattern USN-nnnn."}, "expected_status": "error", "plan": \[\], "title": "USN-123455", "warnings": \[\]}}, {"related_usns_plan": \[\], "target_usn_plan": {"additional_data": {}, "affected_packages": null, "description": null, "error": {"code": "invalid-security-issue", "msg": "Error: issue \\"USN-12\\" is not recognized.\\n\\nCVEs should follow the pattern CVE-yyyy-nnn.\\n\\nUSNs should follow the pattern USN-nnnn."}, "expected_status": "error", "plan": \[\], "title": "USN-12", "warnings": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - - Examples: ubuntu release details - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - Scenario Outline: Fix command on an unattached machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-28196"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"additional_data": {}, "affected_packages": \["krb5"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"pocket": "standard-updates", "source_packages": \["krb5"\], "status": "cve-already-fixed"}, "operation": "no-op", "order": 1}\], "title": "CVE-2020-28196", "warnings": \[\]}\], "expected_status": "fixed"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2022-24959"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"additional_data": {}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}], "title": "CVE-2022-24959", "warnings": \[\]}\], "expected_status": "not-affected"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-28196", "CVE-2022-24959"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"additional_data": {}, "affected_packages": \["krb5"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"pocket": "standard-updates", "source_packages": \["krb5"\], "status": "cve-already-fixed"}, "operation": "no-op", "order": 1}\], "title": "CVE-2020-28196", "warnings": \[\]}, {"additional_data": {}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}], "title": "CVE-2022-24959", "warnings": \[\]}\], "expected_status": "fixed"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I apt update - And I apt install `libawl-php=0.60-1` - And I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-4539-1"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"expected_status": "fixed", "usns": \[{"related_usns_plan": \[\], "target_usn_plan": {"additional_data": {"associated_cves": \["CVE-2020-11728"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \["awl"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"binary_packages": \["libawl-php"\], "pocket": "standard-updates", "source_packages": \["awl"\]}, "operation": "apt-upgrade", "order": 1}\], "title": "USN-4539-1", "warnings": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I apt install `rsync=3.1.3-8 zlib1g=1:1.2.11.dfsg-2ubuntu1` - And I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-5573-1"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"expected_status": "fixed", "usns": \[{"related_usns_plan": \[{"additional_data": {"associated_cves": \["CVE-2022-37434"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-5570-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2022-37434"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \["zlib"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"binary_packages": \["zlib1g"\], "pocket": "standard-updates", "source_packages": \["zlib"\]}, "operation": "apt-upgrade", "order": 1}], "title": "USN-5570-2", "warnings": \[\]}\], "target_usn_plan": {"additional_data": {"associated_cves": \["CVE-2022-37434"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \["rsync"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"binary_packages": \["rsync"\], "pocket": "standard-updates", "source_packages": \["rsync"\]}, "operation": "apt-upgrade", "order": 1}\], "title": "USN-5573-1", "warnings": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-4539-1", "USN-5573-1"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"expected_status": "fixed", "usns": \[{"related_usns_plan": \[\], "target_usn_plan": {"additional_data": {"associated_cves": \["CVE-2020-11728"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \["awl"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"binary_packages": \["libawl-php"\], "pocket": "standard-updates", "source_packages": \["awl"\]}, "operation": "apt-upgrade", "order": 1}\], "title": "USN-4539-1", "warnings": \[\]}}, {"related_usns_plan": \[{"additional_data": {"associated_cves": \["CVE-2022-37434"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-5570-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2022-37434"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \["zlib"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"binary_packages": \["zlib1g"\], "pocket": "standard-updates", "source_packages": \["zlib"\]}, "operation": "apt-upgrade", "order": 1}\], "title": "USN-5570-2", "warnings": \[\]}\], "target_usn_plan": {"additional_data": {"associated_cves": \["CVE-2022-37434"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \["rsync"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"binary_packages": \["rsync"\], "pocket": "standard-updates", "source_packages": \["rsync"\]}, "operation": "apt-upgrade", "order": 1}\], "title": "USN-5573-1", "warnings": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - - Examples: ubuntu release details - | release | machine_type | - | focal | lxd-container | - - Scenario Outline: Fix command on an unattached machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-15180"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"additional_data": {}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}], "title": "CVE-2020-15180", "warnings": \[\]}\], "expected_status": "not-affected"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-28196"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"additional_data": {}, "affected_packages": \["krb5"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"pocket": "standard-updates", "source_packages": \["krb5"\], "status": "cve-already-fixed"}, "operation": "no-op", "order": 1}\], "title": "CVE-2020-28196", "warnings": \[\]}\], "expected_status": "fixed"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I apt update - And I apt install `expat=2.1.0-7 swish-e matanza ghostscript` - And I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2017-9233"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"additional_data": {}, "affected_packages": \["expat", "matanza", "swish-e"\], "description": ".*", "error": null, "expected_status": "still-affected", "plan": \[{"data": {"binary_packages": \["expat"\], "pocket": "standard-updates", "source_packages": \["expat"\]}, "operation": "apt-upgrade", "order": 2}\], "title": "CVE-2017-9233", "warnings": \[{"data": {"source_packages": \["matanza", "swish-e"\], "status": "needs-triage"}, "order": 1, "warning_type": "security-issue-not-fixed"}\]}\], "expected_status": "still-affected"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-28196", "CVE-2020-15180", "CVE-2017-9233"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"additional_data": {}, "affected_packages": \["krb5"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"pocket": "standard-updates", "source_packages": \["krb5"\], "status": "cve-already-fixed"}, "operation": "no-op", "order": 1}\], "title": "CVE-2020-28196", "warnings": \[\]}, {"additional_data": {}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "CVE-2020-15180", "warnings": \[\]}, {"additional_data": {}, "affected_packages": \["expat", "matanza", "swish-e"\], "description": ".*", "error": null, "expected_status": "still\-affected", "plan": \[{"data": {"binary_packages": \["expat"\], "pocket": "standard-updates", "source_packages": \["expat"\]}, "operation": "apt-upgrade", "order": 2}\], "title": "CVE-2017-9233", "warnings": \[{"data": {"source_packages": \["matanza", "swish\-e"\], "status": "needs-triage"}, "order": 1, "warning_type": "security\-issue-not\-fixed"}\]}\], "expected_status": "still\-affected"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I apt install `libawl-php` - And I reboot the machine - And I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-4539-1"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"expected_status": "not-affected", "usns": \[{"related_usns_plan": \[\], "target_usn_plan": {"additional_data": {"associated_cves": \["CVE-2020-11728"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-4539-1", "warnings": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-5079-2"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"expected_status": "fixed", "usns": \[{"related_usns_plan": \[{"additional_data": {"associated_cves": \["CVE-2021-22947", "CVE-2021-22945", "CVE-2021-22946"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-5079-1", "warnings": \[\]}\], "target_usn_plan": {"additional_data": {"associated_cves": \["CVE-2021-22946", "CVE-2021-22947"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \["curl"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"reason": "required-pro-service", "required_service": "esm-infra", "source_packages": \["curl"\]}, "operation": "attach", "order": 1}, {"data": {"service": "esm-infra", "source_packages": \["curl"\]}, "operation": "enable", "order": 2}, {"data": {"binary_packages": \["curl", "libcurl3-gnutls"\], "pocket": "esm-infra", "source_packages": \["curl"\]}, "operation": "apt-upgrade", "order": 3}\], "title": "USN-5079-2", "warnings": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-5051-2"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"expected_status": "fixed", "usns": \[{"related_usns_plan": \[{"additional_data": {"associated_cves": \["CVE-2021-3711", "CVE-2021-3712"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-5051-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2021-3712"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-5051-3", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2019-11098", "CVE-2021-3712", "CVE-2021-23840", "CVE-2021-38575"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-5088-1", "warnings": \[\]}\], "target_usn_plan": {"additional_data": {"associated_cves": \["CVE-2021-3712"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \["openssl"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"reason": "required-pro-service", "required_service": "esm-infra", "source_packages": \["openssl"\]}, "operation": "attach", "order": 1}, {"data": {"service": "esm-infra", "source_packages": \["openssl"\]}, "operation": "enable", "order": 2}, {"data": {"binary_packages": \["libssl1.0.0", "openssl"\], "pocket": "esm-infra", "source_packages": \["openssl"\]}, "operation": "apt-upgrade", "order": 3}\], "title": "USN-5051-2", "warnings": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-5378-4"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"expected_status": "fixed", "usns": \[{"related_usns_plan": \[{"additional_data": {"associated_cves": \["CVE-2022-1271"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-5378-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2022-1271"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-5378-2", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2022-1271"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \["xz-utils"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"reason": "required-pro-service", "required_service": "esm-infra", "source_packages": \["xz-utils"\]}, "operation": "attach", "order": 1}, {"data": {"service": "esm-infra", "source_packages": \["xz-utils"\]}, "operation": "enable", "order": 2}, {"data": {"binary_packages": \["liblzma5", "xz-utils"\], "pocket": "esm-infra", "source_packages": \["xz-utils"\]}, "operation": "apt-upgrade", "order": 3}\], "title": "USN-5378-3", "warnings": \[\]}], "target_usn_plan": {"additional_data": {"associated_cves": \["CVE-2022-1271"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \["gzip"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"reason": "required-pro-service", "required_service": "esm-infra", "source_packages": \["gzip"\]}, "operation": "attach", "order": 1}, {"data": {"service": "esm-infra", "source_packages": \["gzip"\]}, "operation": "enable", "order": 2}, {"data": {"binary_packages": \["gzip"\], "pocket": "esm-infra", "source_packages": \["gzip"\]}, "operation": "apt-upgrade", "order": 3}\], "title": "USN-5378-4", "warnings": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-5051-2", "USN-5378-4"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"expected_status": "fixed", "usns": \[{"related_usns_plan": \[{"additional_data": {"associated_cves": \["CVE-2021-3711", "CVE-2021-3712"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-5051-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2021-3712"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-5051-3", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2019-11098", "CVE-2021-3712", "CVE-2021-23840", "CVE-2021-38575"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-5088-1", "warnings": \[\]}\], "target_usn_plan": {"additional_data": {"associated_cves": \["CVE-2021-3712"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \["openssl"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"reason": "required-pro-service", "required_service": "esm-infra", "source_packages": \["openssl"\]}, "operation": "attach", "order": 1}, {"data": {"service": "esm-infra", "source_packages": \["openssl"\]}, "operation": "enable", "order": 2}, {"data": {"binary_packages": \["libssl1.0.0", "openssl"\], "pocket": "esm-infra", "source_packages": \["openssl"\]}, "operation": "apt-upgrade", "order": 3}\], "title": "USN-5051-2", "warnings": \[\]}}, {"related_usns_plan": \[{"additional_data": {"associated_cves": \["CVE-2022-1271"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-5378-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2022-1271"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-5378-2", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2022-1271"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \["xz-utils"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"reason": "required-pro-service", "required_service": "esm-infra", "source_packages": \["xz-utils"\]}, "operation": "attach", "order": 1}, {"data": {"service": "esm-infra", "source_packages": \["xz-utils"\]}, "operation": "enable", "order": 2}, {"data": {"binary_packages": \["liblzma5", "xz-utils"\], "pocket": "esm-infra", "source_packages": \["xz-utils"\]}, "operation": "apt-upgrade", "order": 3}\], "title": "USN-5378-3", "warnings": \[\]}\], "target_usn_plan": {"additional_data": {"associated_cves": \["CVE-2022-1271"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \["gzip"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"reason": "required-pro-service", "required_service": "esm-infra", "source_packages": \["gzip"\]}, "operation": "attach", "order": 1}, {"data": {"service": "esm-infra", "source_packages": \["gzip"\]}, "operation": "enable", "order": 2}, {"data": {"binary_packages": \["gzip"\], "pocket": "esm-infra", "source_packages": \["gzip"\]}, "operation": "apt-upgrade", "order": 3}\], "title": "USN-5378-4", "warnings": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `sed -i "/xenial-updates/d" /etc/apt/sources.list` with sudo - And I run `sed -i "/xenial-security/d" /etc/apt/sources.list` with sudo - And I apt update - And I apt install `squid` - And I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-25097"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"additional_data": {}, "affected_packages": \["squid3"\], "description": ".*", "error": null, "expected_status": "still-affected", "plan": \[{"data": {"binary_packages": \[\], "pocket": "standard-updates", "source_packages": \["squid3"\]}, "operation": "apt-upgrade", "order": 3}\], "title": "CVE-2020-25097", "warnings": \[{"data": {"binary_package": "squid", "binary_package_version": "3.5.12-1ubuntu7.16", "pocket": "standard-updates", "related_source_packages": \["squid3"\], "source_package": "squid3"}, "order": 1, "warning_type": "package-cannot-be-installed"}, {"data": {"binary_package": "squid-common", "binary_package_version": "3.5.12-1ubuntu7.16", "pocket": "standard-updates", "related_source_packages": \["squid3"\], "source_package": "squid3"}, "order": 2, "warning_type": "package-cannot-be-installed"}\]}\], "expected_status": "still-affected"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - - Examples: ubuntu release details - | release | machine_type | - | xenial | lxd-container | - - Scenario Outline: Fix command on an unattached machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-28196"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"additional_data": {}, "affected_packages": \["krb5"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"pocket": "standard-updates", "source_packages": \["krb5"\], "status": "cve-already-fixed"}, "operation": "no-op", "order": 1}\], "title": "CVE-2020-28196", "warnings": \[\]}\], "expected_status": "fixed"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I apt update - And I apt install `xterm=330-1ubuntu2` - And I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2021-27135"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"additional_data": {}, "affected_packages": \["xterm"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"binary_packages": \["xterm"\], "pocket": "standard-updates", "source_packages": \["xterm"\]}, "operation": "apt-upgrade", "order": 1}\], "title": "CVE-2021-27135", "warnings": \[\]}\], "expected_status": "fixed"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I apt install `libawl-php` - And I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-4539-1"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"expected_status": "not-affected", "usns": \[{"related_usns_plan": \[\], "target_usn_plan": {"additional_data": {"associated_cves": \["CVE-2020-11728"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-4539-1", "warnings": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I apt install `libbz2-1.0=1.0.6-8.1 bzip2=1.0.6-8.1` - And I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-4038-3"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"expected_status": "fixed", "usns": \[{"related_usns_plan": \[\], "target_usn_plan": {"additional_data": {"associated_cves": \[\], "associated_launchpad_bugs": \["https://launchpad.net/bugs/1834494"\]}, "affected_packages": \["bzip2"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"binary_packages": \["bzip2", "libbz2-1.0"\], "pocket": "standard-updates", "source_packages": \["bzip2"\]}, "operation": "apt-upgrade", "order": 1}\], "title": "USN-4038-3", "warnings": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-6130-1"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"expected_status": "not-affected", "usns": \[{"related_usns_plan": \[{"additional_data": {"associated_cves": \["CVE-2023-1076", "CVE-2023-1118", "CVE-2023-25012", "CVE-2023-1855", "CVE-2023-1990", "CVE-2023-28866", "CVE-2023-1998", "CVE-2023-1077", "CVE-2023-1583", "CVE-2023-1670", "CVE-2023-1032", "CVE-2023-1079", "CVE-2023-30456", "CVE-2023-28466", "CVE-2023-1989", "CVE-2023-1829", "CVE-2022-4269"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6033-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-32233", "CVE-2023-2612"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6122-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-30456", "CVE-2023-2612", "CVE-2023-1670", "CVE-2023-26606", "CVE-2023-32233"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6123-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-30456", "CVE-2023-32233", "CVE-2023-2612", "CVE-2022-4139", "CVE-2022-3586", "CVE-2023-1670"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6124-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-32233", "CVE-2023-1380", "CVE-2023-2612", "CVE-2023-31436", "CVE-2023-30456"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6127-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-30456", "CVE-2023-1380", "CVE-2023-32233", "CVE-2023-2612", "CVE-2023-31436"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6131-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-0459", "CVE-2023-30456", "CVE-2023-1380", "CVE-2023-1075", "CVE-2023-2162", "CVE-2023-32233", "CVE-2023-2612", "CVE-2022-3707", "CVE-2023-1118", "CVE-2023-1513", "CVE-2023-32269", "CVE-2023-31436", "CVE-2023-1078"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6132-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-32233", "CVE-2023-1380", "CVE-2023-2612", "CVE-2023-31436", "CVE-2023-30456"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6135-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-32233", "CVE-2023-28328", "CVE-2023-1073", "CVE-2023-30456", "CVE-2023-1380", "CVE-2023-31436"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6149-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-30456", "CVE-2023-1380", "CVE-2023-32233", "CVE-2023-2612", "CVE-2023-31436"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6150-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-31436", "CVE-2023-2612", "CVE-2023-30456", "CVE-2023-1380", "CVE-2023-32233"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6162-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-32254", "CVE-2023-2002", "CVE-2023-2156", "CVE-2023-32250", "CVE-2023-2269", "CVE-2023-1380", "CVE-2023-31436"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6173-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-33203", "CVE-2023-1859", "CVE-2023-1855", "CVE-2023-33288", "CVE-2023-2194", "CVE-2023-30456", "CVE-2023-32233", "CVE-2023-2235", "CVE-2023-2612", "CVE-2023-28466", "CVE-2023-1380", "CVE-2023-1611", "CVE-2023-1990", "CVE-2023-31436", "CVE-2023-1989", "CVE-2023-1583", "CVE-2023-28866", "CVE-2023-30772", "CVE-2023-1670", "CVE-2022-4269"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6175-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-33203", "CVE-2023-1859", "CVE-2023-1855", "CVE-2023-33288", "CVE-2023-2194", "CVE-2023-30456", "CVE-2023-32233", "CVE-2023-2235", "CVE-2023-2612", "CVE-2023-28466", "CVE-2023-1380", "CVE-2023-1611", "CVE-2023-1990", "CVE-2023-31436", "CVE-2023-1989", "CVE-2023-1583", "CVE-2023-28866", "CVE-2023-30772", "CVE-2023-1670", "CVE-2022-4269"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6186-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-1380", "CVE-2023-32233", "CVE-2022-4129", "CVE-2023-2162", "CVE-2023-26545", "CVE-2022-3108", "CVE-2023-1670", "CVE-2023-1998", "CVE-2022-3707", "CVE-2023-1281", "CVE-2023-1118", "CVE-2023-30456", "CVE-2023-0459", "CVE-2023-2985", "CVE-2023-1074", "CVE-2023-2612", "CVE-2023-1859", "CVE-2023-32269", "CVE-2023-1076", "CVE-2022-3903", "CVE-2023-1073", "CVE-2023-1079", "CVE-2023-0458", "CVE-2023-1829", "CVE-2023-1078", "CVE-2023-3161", "CVE-2023-25012", "CVE-2023-1075", "CVE-2023-1513", "CVE-2023-1077", "CVE-2023-31436"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6222-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-2162", "CVE-2022-3707", "CVE-2023-1078", "CVE-2022-4129", "CVE-2023-0459", "CVE-2023-1859", "CVE-2023-1077", "CVE-2023-1079", "CVE-2023-32269", "CVE-2023-0458", "CVE-2022-3903", "CVE-2023-3161", "CVE-2023-25012", "CVE-2023-30456", "CVE-2023-35788", "CVE-2023-2612", "CVE-2023-1829", "CVE-2023-32233", "CVE-2023-31436", "CVE-2023-1380", "CVE-2023-26545", "CVE-2023-1075", "CVE-2023-1998", "CVE-2022-3108", "CVE-2023-1513", "CVE-2023-1074", "CVE-2023-1073", "CVE-2023-1281", "CVE-2023-1670", "CVE-2023-2985", "CVE-2023-1118", "CVE-2023-1076"\], "associated_launchpad_bugs": \["https://launchpad.net/bugs/2023220", "https://launchpad.net/bugs/2023577"\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6256-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-3141", "CVE-2023-40283", "CVE-2023-28328", "CVE-2023-3220", "CVE-2023-1206", "CVE-2023-1075", "CVE-2023-4273", "CVE-2023-4015", "CVE-2022-27672", "CVE-2023-1076", "CVE-2023-28466", "CVE-2023-3609", "CVE-2023-4128", "CVE-2023-2898", "CVE-2023-3090", "CVE-2023-2235", "CVE-2023-2002", "CVE-2023-32269", "CVE-2023-3863", "CVE-2023-31436", "CVE-2023-2163", "CVE-2023-3777", "CVE-2023-3390", "CVE-2023-3611", "CVE-2023-3776", "CVE-2023-0458", "CVE-2023-4004", "CVE-2023-4194", "CVE-2022-4269", "CVE-2023-20593", "CVE-2023-1611", "CVE-2023-2269", "CVE-2023-1380", "CVE-2023-3995", "CVE-2023-2162", "CVE-2023-3610", "CVE-2023-4569"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6385-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2023-42755", "CVE-2023-1380", "CVE-2023-42752", "CVE-2023-35001", "CVE-2023-1206", "CVE-2023-4623", "CVE-2023-31436"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6460-1", "warnings": \[\]}\], "target_usn_plan": {"additional_data": {"associated_cves": \["CVE-2023-30456", "CVE-2023-1380", "CVE-2023-32233", "CVE-2023-31436"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-6130-1", "warnings": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-4539-1", "USN-4038-1"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `usn_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"usns_data": {"expected_status": "fixed", "usns": \[{"related_usns_plan": \[\], "target_usn_plan": {"additional_data": {"associated_cves": \["CVE-2020-11728"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-4539-1", "warnings": \[\]}}, {"related_usns_plan": \[{"additional_data": {"associated_cves": \["CVE-2016-3189", "CVE-2019-12900"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-4038-2", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2019-12625", "CVE-2019-12900"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-4146-1", "warnings": \[\]}, {"additional_data": {"associated_cves": \["CVE-2019-12625", "CVE-2019-12900"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "USN-4146-2", "warnings": \[\]}\], "target_usn_plan": {"additional_data": {"associated_cves": \["CVE-2016-3189", "CVE-2019-12900"\], "associated_launchpad_bugs": \[\]}, "affected_packages": \["bzip2"\], "description": ".*", "error": null, "expected_status": "fixed", "plan": \[{"data": {"binary_packages": \["bzip2", "libbz2-1.0"\], "pocket": "standard-updates", "source_packages": \["bzip2"\]}, "operation": "apt-upgrade", "order": 1}\], "title": "USN-4038-1", "warnings": \[\]}}\]}}, "meta": {"environment_vars": \[\]}, "type": "USNFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - - Examples: ubuntu release details - | release | machine_type | - | bionic | lxd-container | - - Scenario Outline: Fix command on an unattached machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2022-40982"]}'` as non-root - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `cve_fix_plan` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"cves_data": {"cves": \[{"additional_data": {}, "affected_packages": \[\], "description": ".*", "error": null, "expected_status": "not-affected", "plan": \[{"data": {"status": "system-not-affected"}, "operation": "no-op", "order": 1}\], "title": "CVE-2022-40982", "warnings": \[\]}\], "expected_status": "not-affected"}}, "meta": {"environment_vars": \[\]}, "type": "CVEFixPlan"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - - Examples: ubuntu release details - | release | machine_type | - | mantic | lxd-vm | + Scenario Outline: Fix command on an unattached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-1800-123456"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "additional_data": {}, + "affected_packages": null, + "description": null, + "error": { + "code": "security-fix-not-found-issue", + "msg": "Error: CVE-1800-123456 not found." + }, + "expected_status": "error", + "plan": [], + "title": "CVE-1800-123456", + "warnings": [] + } + ], + "expected_status": "error" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixPlan" + } + """ + When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-123455"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "expected_status": "error", + "usns": [ + { + "related_usns_plan": [], + "target_usn_plan": { + "additional_data": {}, + "affected_packages": null, + "description": null, + "error": { + "code": "invalid-security-issue", + "msg": "Error: issue \\"USN-123455\\" is not recognized.\n\nCVEs should follow the pattern CVE-yyyy-nnn.\n\nUSNs should follow the pattern USN-nnnn." + }, + "expected_status": "error", + "plan": [], + "title": "USN-123455", + "warnings": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixPlan" + } + """ + When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-123455", "CVE-12"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "additional_data": {}, + "affected_packages": null, + "description": null, + "error": { + "code": "invalid-security-issue", + "msg": "Error: issue \\"CVE-123455\\" is not recognized.\n\nCVEs should follow the pattern CVE-yyyy-nnn.\n\nUSNs should follow the pattern USN-nnnn." + }, + "expected_status": "error", + "plan": [], + "title": "CVE-123455", + "warnings": [] + }, + { + "additional_data": {}, + "affected_packages": null, + "description": null, + "error": { + "code": "invalid-security-issue", + "msg": "Error: issue \\"CVE-12\\" is not recognized.\n\nCVEs should follow the pattern CVE-yyyy-nnn.\n\nUSNs should follow the pattern USN-nnnn." + }, + "expected_status": "error", + "plan": [], + "title": "CVE-12", + "warnings": [] + } + ], + "expected_status": "error" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixPlan" + } + """ + When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-123455", "USN-12"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "expected_status": "error", + "usns": [ + { + "related_usns_plan": [], + "target_usn_plan": { + "additional_data": {}, + "affected_packages": null, + "description": null, + "error": { + "code": "invalid-security-issue", + "msg": "Error: issue \\"USN-123455\\" is not recognized.\n\nCVEs should follow the pattern CVE-yyyy-nnn.\n\nUSNs should follow the pattern USN-nnnn." + }, + "expected_status": "error", + "plan": [], + "title": "USN-123455", + "warnings": [] + } + }, + { + "related_usns_plan": [], + "target_usn_plan": { + "additional_data": {}, + "affected_packages": null, + "description": null, + "error": { + "code": "invalid-security-issue", + "msg": "Error: issue \\"USN-12\\" is not recognized.\n\nCVEs should follow the pattern CVE-yyyy-nnn.\n\nUSNs should follow the pattern USN-nnnn." + }, + "expected_status": "error", + "plan": [], + "title": "USN-12", + "warnings": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixPlan" + } + """ + + Examples: ubuntu release details + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + Scenario Outline: Fix command on an unattached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-28196"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "additional_data": {}, + "affected_packages": [ + "krb5" + ], + "description": "Kerberos vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "pocket": "standard-updates", + "source_packages": [ + "krb5" + ], + "status": "cve-already-fixed" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "CVE-2020-28196", + "warnings": [] + } + ], + "expected_status": "fixed" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixPlan" + } + """ + When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2022-24959"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "additional_data": {}, + "affected_packages": [], + "description": "Linux kernel vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "CVE-2022-24959", + "warnings": [] + } + ], + "expected_status": "not-affected" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixPlan" + } + """ + When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-28196", "CVE-2022-24959"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "additional_data": {}, + "affected_packages": [ + "krb5" + ], + "description": "Kerberos vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "pocket": "standard-updates", + "source_packages": [ + "krb5" + ], + "status": "cve-already-fixed" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "CVE-2020-28196", + "warnings": [] + }, + { + "additional_data": {}, + "affected_packages": [], + "description": "Linux kernel vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "CVE-2022-24959", + "warnings": [] + } + ], + "expected_status": "fixed" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixPlan" + } + """ + When I apt install `libawl-php=0.60-1` + And I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-4539-1"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "expected_status": "fixed", + "usns": [ + { + "related_usns_plan": [], + "target_usn_plan": { + "additional_data": { + "associated_cves": [ + "CVE-2020-11728" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "awl" + ], + "description": "AWL vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "binary_packages": [ + "libawl-php" + ], + "pocket": "standard-updates", + "source_packages": [ + "awl" + ] + }, + "operation": "apt-upgrade", + "order": 1 + } + ], + "title": "USN-4539-1", + "warnings": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixPlan" + } + """ + When I apt install `rsync=3.1.3-8 zlib1g=1:1.2.11.dfsg-2ubuntu1` + And I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-5573-1"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_plan` schema + And API data field output matches regexp: + """ + "attributes": { + "usns_data": { + "expected_status": "fixed", + "usns": [ + { + "related_usns_plan": [ + { + "additional_data": { + "associated_cves": [ + "CVE-2022-37434" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "zlib vulnerability", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-5570-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2022-37434" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "zlib" + ], + "description": "zlib vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "binary_packages": [ + "zlib1g" + ], + "pocket": "standard-updates", + "source_packages": [ + "zlib" + ] + }, + "operation": "apt-upgrade", + "order": 1 + } + ], + "title": "USN-5570-2", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2018-25032", + "CVE-2016-9840", + "CVE-2022-37434", + "CVE-2016-9841" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "klibc" + ], + "description": "klibc vulnerabilities", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "pocket": "standard-updates", + "source_packages": [ + "klibc" + ], + "status": "cve-already-fixed" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6736-1", + "warnings": [] + } + ], + "target_usn_plan": { + "additional_data": { + "associated_cves": [ + "CVE-2022-37434" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "rsync" + ], + "description": "rsync vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "binary_packages": [ + "rsync" + ], + "pocket": "standard-updates", + "source_packages": [ + "rsync" + ] + }, + "operation": "apt-upgrade", + "order": 1 + } + ], + "title": "USN-5573-1", + "warnings": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixPlan" + } + """ + When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-4539-1", "USN-5573-1"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "expected_status": "fixed", + "usns": [ + { + "related_usns_plan": [], + "target_usn_plan": { + "additional_data": { + "associated_cves": [ + "CVE-2020-11728" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "awl" + ], + "description": "AWL vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "binary_packages": [ + "libawl-php" + ], + "pocket": "standard-updates", + "source_packages": [ + "awl" + ] + }, + "operation": "apt-upgrade", + "order": 1 + } + ], + "title": "USN-4539-1", + "warnings": [] + } + }, + { + "related_usns_plan": [ + { + "additional_data": { + "associated_cves": [ + "CVE-2022-37434" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "zlib vulnerability", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-5570-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2022-37434" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "zlib" + ], + "description": "zlib vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "binary_packages": [ + "zlib1g" + ], + "pocket": "standard-updates", + "source_packages": [ + "zlib" + ] + }, + "operation": "apt-upgrade", + "order": 1 + } + ], + "title": "USN-5570-2", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2018-25032", + "CVE-2016-9840", + "CVE-2022-37434", + "CVE-2016-9841" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "klibc" + ], + "description": "klibc vulnerabilities", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "pocket": "standard-updates", + "source_packages": [ + "klibc" + ], + "status": "cve-already-fixed" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6736-1", + "warnings": [] + } + ], + "target_usn_plan": { + "additional_data": { + "associated_cves": [ + "CVE-2022-37434" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "rsync" + ], + "description": "rsync vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "binary_packages": [ + "rsync" + ], + "pocket": "standard-updates", + "source_packages": [ + "rsync" + ] + }, + "operation": "apt-upgrade", + "order": 1 + } + ], + "title": "USN-5573-1", + "warnings": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixPlan" + } + """ + + Examples: ubuntu release details + | release | machine_type | + | focal | lxd-container | + + Scenario Outline: Fix command on an unattached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-15180"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "additional_data": {}, + "affected_packages": [], + "description": "MariaDB vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "CVE-2020-15180", + "warnings": [] + } + ], + "expected_status": "not-affected" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixPlan" + } + """ + When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-28196"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "additional_data": {}, + "affected_packages": [ + "krb5" + ], + "description": "Kerberos vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "pocket": "standard-updates", + "source_packages": [ + "krb5" + ], + "status": "cve-already-fixed" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "CVE-2020-28196", + "warnings": [] + } + ], + "expected_status": "fixed" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixPlan" + } + """ + When I apt install `expat=2.1.0-7 swish-e matanza ghostscript` + And I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2017-9233"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "additional_data": {}, + "affected_packages": [ + "expat", + "matanza", + "swish-e" + ], + "description": "Expat vulnerability", + "error": null, + "expected_status": "still-affected", + "plan": [ + { + "data": { + "binary_packages": [ + "expat" + ], + "pocket": "standard-updates", + "source_packages": [ + "expat" + ] + }, + "operation": "apt-upgrade", + "order": 2 + } + ], + "title": "CVE-2017-9233", + "warnings": [ + { + "data": { + "source_packages": [ + "matanza", + "swish-e" + ], + "status": "needs-triage" + }, + "order": 1, + "warning_type": "security-issue-not-fixed" + } + ] + } + ], + "expected_status": "still-affected" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixPlan" + } + """ + When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-28196", "CVE-2020-15180", "CVE-2017-9233"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_plan` schema + And API data field output matches regexp: + """ + "attributes": { + "cves_data": { + "cves": [ + { + "additional_data": {}, + "affected_packages": [ + "krb5" + ], + "description": "Kerberos vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "pocket": "standard-updates", + "source_packages": [ + "krb5" + ], + "status": "cve-already-fixed" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "CVE-2020-28196", + "warnings": [] + }, + { + "additional_data": {}, + "affected_packages": [], + "description": "MariaDB vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "CVE-2020-15180", + "warnings": [] + }, + { + "additional_data": {}, + "affected_packages": [ + "expat", + "matanza", + "swish-e" + ], + "description": "Expat vulnerability", + "error": null, + "expected_status": "still-affected", + "plan": [ + { + "data": { + "binary_packages": [ + "expat" + ], + "pocket": "standard-updates", + "source_packages": [ + "expat" + ] + }, + "operation": "apt-upgrade", + "order": 2 + } + ], + "title": "CVE-2017-9233", + "warnings": [ + { + "data": { + "source_packages": [ + "matanza", + "swish-e" + ], + "status": "needs-triage" + }, + "order": 1, + "warning_type": "security-issue-not-fixed" + } + ] + } + ], + "expected_status": "still-affected" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixPlan" + } + """ + When I apt install `libawl-php` + And I reboot the machine + And I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-4539-1"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "expected_status": "not-affected", + "usns": [ + { + "related_usns_plan": [], + "target_usn_plan": { + "additional_data": { + "associated_cves": [ + "CVE-2020-11728" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "AWL vulnerability", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-4539-1", + "warnings": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixPlan" + } + """ + When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-5079-2"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "expected_status": "fixed", + "usns": [ + { + "related_usns_plan": [ + { + "additional_data": { + "associated_cves": [ + "CVE-2021-22947", + "CVE-2021-22945", + "CVE-2021-22946" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "curl vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-5079-1", + "warnings": [] + } + ], + "target_usn_plan": { + "additional_data": { + "associated_cves": [ + "CVE-2021-22946", + "CVE-2021-22947" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "curl" + ], + "description": "curl vulnerabilities", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "reason": "required-pro-service", + "required_service": "esm-infra", + "source_packages": [ + "curl" + ] + }, + "operation": "attach", + "order": 1 + }, + { + "data": { + "service": "esm-infra", + "source_packages": [ + "curl" + ] + }, + "operation": "enable", + "order": 2 + }, + { + "data": { + "binary_packages": [ + "curl", + "libcurl3-gnutls" + ], + "pocket": "esm-infra", + "source_packages": [ + "curl" + ] + }, + "operation": "apt-upgrade", + "order": 3 + } + ], + "title": "USN-5079-2", + "warnings": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixPlan" + } + """ + When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-5051-2"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "expected_status": "fixed", + "usns": [ + { + "related_usns_plan": [ + { + "additional_data": { + "associated_cves": [ + "CVE-2021-3711", + "CVE-2021-3712" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "OpenSSL vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-5051-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2021-3712" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "OpenSSL vulnerability", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-5051-3", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2019-11098", + "CVE-2021-3712", + "CVE-2021-23840", + "CVE-2021-38575" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "EDK II vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-5088-1", + "warnings": [] + } + ], + "target_usn_plan": { + "additional_data": { + "associated_cves": [ + "CVE-2021-3712" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "openssl" + ], + "description": "OpenSSL vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "reason": "required-pro-service", + "required_service": "esm-infra", + "source_packages": [ + "openssl" + ] + }, + "operation": "attach", + "order": 1 + }, + { + "data": { + "service": "esm-infra", + "source_packages": [ + "openssl" + ] + }, + "operation": "enable", + "order": 2 + }, + { + "data": { + "binary_packages": [ + "libssl1.0.0", + "openssl" + ], + "pocket": "esm-infra", + "source_packages": [ + "openssl" + ] + }, + "operation": "apt-upgrade", + "order": 3 + } + ], + "title": "USN-5051-2", + "warnings": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixPlan" + } + """ + When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-5378-4"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "expected_status": "fixed", + "usns": [ + { + "related_usns_plan": [ + { + "additional_data": { + "associated_cves": [ + "CVE-2022-1271" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Gzip vulnerability", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-5378-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2022-1271" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "XZ Utils vulnerability", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-5378-2", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2022-1271" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "xz-utils" + ], + "description": "XZ Utils vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "reason": "required-pro-service", + "required_service": "esm-infra", + "source_packages": [ + "xz-utils" + ] + }, + "operation": "attach", + "order": 1 + }, + { + "data": { + "service": "esm-infra", + "source_packages": [ + "xz-utils" + ] + }, + "operation": "enable", + "order": 2 + }, + { + "data": { + "binary_packages": [ + "liblzma5", + "xz-utils" + ], + "pocket": "esm-infra", + "source_packages": [ + "xz-utils" + ] + }, + "operation": "apt-upgrade", + "order": 3 + } + ], + "title": "USN-5378-3", + "warnings": [] + } + ], + "target_usn_plan": { + "additional_data": { + "associated_cves": [ + "CVE-2022-1271" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "gzip" + ], + "description": "Gzip vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "reason": "required-pro-service", + "required_service": "esm-infra", + "source_packages": [ + "gzip" + ] + }, + "operation": "attach", + "order": 1 + }, + { + "data": { + "service": "esm-infra", + "source_packages": [ + "gzip" + ] + }, + "operation": "enable", + "order": 2 + }, + { + "data": { + "binary_packages": [ + "gzip" + ], + "pocket": "esm-infra", + "source_packages": [ + "gzip" + ] + }, + "operation": "apt-upgrade", + "order": 3 + } + ], + "title": "USN-5378-4", + "warnings": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixPlan" + } + """ + When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-5051-2", "USN-5378-4"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "expected_status": "fixed", + "usns": [ + { + "related_usns_plan": [ + { + "additional_data": { + "associated_cves": [ + "CVE-2021-3711", + "CVE-2021-3712" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "OpenSSL vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-5051-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2021-3712" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "OpenSSL vulnerability", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-5051-3", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2019-11098", + "CVE-2021-3712", + "CVE-2021-23840", + "CVE-2021-38575" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "EDK II vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-5088-1", + "warnings": [] + } + ], + "target_usn_plan": { + "additional_data": { + "associated_cves": [ + "CVE-2021-3712" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "openssl" + ], + "description": "OpenSSL vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "reason": "required-pro-service", + "required_service": "esm-infra", + "source_packages": [ + "openssl" + ] + }, + "operation": "attach", + "order": 1 + }, + { + "data": { + "service": "esm-infra", + "source_packages": [ + "openssl" + ] + }, + "operation": "enable", + "order": 2 + }, + { + "data": { + "binary_packages": [ + "libssl1.0.0", + "openssl" + ], + "pocket": "esm-infra", + "source_packages": [ + "openssl" + ] + }, + "operation": "apt-upgrade", + "order": 3 + } + ], + "title": "USN-5051-2", + "warnings": [] + } + }, + { + "related_usns_plan": [ + { + "additional_data": { + "associated_cves": [ + "CVE-2022-1271" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Gzip vulnerability", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-5378-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2022-1271" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "XZ Utils vulnerability", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-5378-2", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2022-1271" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "xz-utils" + ], + "description": "XZ Utils vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "reason": "required-pro-service", + "required_service": "esm-infra", + "source_packages": [ + "xz-utils" + ] + }, + "operation": "attach", + "order": 1 + }, + { + "data": { + "service": "esm-infra", + "source_packages": [ + "xz-utils" + ] + }, + "operation": "enable", + "order": 2 + }, + { + "data": { + "binary_packages": [ + "liblzma5", + "xz-utils" + ], + "pocket": "esm-infra", + "source_packages": [ + "xz-utils" + ] + }, + "operation": "apt-upgrade", + "order": 3 + } + ], + "title": "USN-5378-3", + "warnings": [] + } + ], + "target_usn_plan": { + "additional_data": { + "associated_cves": [ + "CVE-2022-1271" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "gzip" + ], + "description": "Gzip vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "reason": "required-pro-service", + "required_service": "esm-infra", + "source_packages": [ + "gzip" + ] + }, + "operation": "attach", + "order": 1 + }, + { + "data": { + "service": "esm-infra", + "source_packages": [ + "gzip" + ] + }, + "operation": "enable", + "order": 2 + }, + { + "data": { + "binary_packages": [ + "gzip" + ], + "pocket": "esm-infra", + "source_packages": [ + "gzip" + ] + }, + "operation": "apt-upgrade", + "order": 3 + } + ], + "title": "USN-5378-4", + "warnings": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixPlan" + } + """ + When I run `sed -i "/xenial-updates/d" /etc/apt/sources.list` with sudo + And I run `sed -i "/xenial-security/d" /etc/apt/sources.list` with sudo + And I apt update + And I apt install `squid` + And I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-25097"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "additional_data": {}, + "affected_packages": [ + "squid3" + ], + "description": "Squid vulnerabilities", + "error": null, + "expected_status": "still-affected", + "plan": [ + { + "data": { + "binary_packages": [], + "pocket": "standard-updates", + "source_packages": [ + "squid3" + ] + }, + "operation": "apt-upgrade", + "order": 3 + } + ], + "title": "CVE-2020-25097", + "warnings": [ + { + "data": { + "binary_package": "squid", + "binary_package_version": ".*", + "pocket": "standard-updates", + "related_source_packages": [ + "squid3" + ], + "source_package": "squid3" + }, + "order": 1, + "warning_type": "package-cannot-be-installed" + }, + { + "data": { + "binary_package": "squid-common", + "binary_package_version": ".*", + "pocket": "standard-updates", + "related_source_packages": [ + "squid3" + ], + "source_package": "squid3" + }, + "order": 2, + "warning_type": "package-cannot-be-installed" + } + ] + } + ], + "expected_status": "still-affected" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixPlan" + } + """ + + Examples: ubuntu release details + | release | machine_type | + | xenial | lxd-container | + + Scenario Outline: Fix command on an unattached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2020-28196"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_plan` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "additional_data": {}, + "affected_packages": [ + "krb5" + ], + "description": "Kerberos vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "pocket": "standard-updates", + "source_packages": [ + "krb5" + ], + "status": "cve-already-fixed" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "CVE-2020-28196", + "warnings": [] + } + ], + "expected_status": "fixed" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixPlan" + } + """ + When I apt install `xterm=330-1ubuntu2` + And I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2021-27135"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_plan` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "additional_data": {}, + "affected_packages": [ + "xterm" + ], + "description": "xterm vulnerability", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "binary_packages": [ + "xterm" + ], + "pocket": "standard-updates", + "source_packages": [ + "xterm" + ] + }, + "operation": "apt-upgrade", + "order": 1 + } + ], + "title": "CVE-2021-27135", + "warnings": [] + } + ], + "expected_status": "fixed" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixPlan" + } + """ + When I apt install `libawl-php` + And I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-4539-1"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_plan` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "expected_status": "not-affected", + "usns": [ + { + "related_usns_plan": [], + "target_usn_plan": { + "additional_data": { + "associated_cves": [ + "CVE-2020-11728" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "AWL vulnerability", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-4539-1", + "warnings": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixPlan" + } + """ + When I apt install `libbz2-1.0=1.0.6-8.1 bzip2=1.0.6-8.1` + And I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-4038-3"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_plan` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "expected_status": "fixed", + "usns": [ + { + "related_usns_plan": [], + "target_usn_plan": { + "additional_data": { + "associated_cves": [], + "associated_launchpad_bugs": [ + "https://launchpad.net/bugs/1834494" + ] + }, + "affected_packages": [ + "bzip2" + ], + "description": "bzip2 regression", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "binary_packages": [ + "bzip2", + "libbz2-1.0" + ], + "pocket": "standard-updates", + "source_packages": [ + "bzip2" + ] + }, + "operation": "apt-upgrade", + "order": 1 + } + ], + "title": "USN-4038-3", + "warnings": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixPlan" + } + """ + When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-6130-1"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_plan` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "expected_status": "not-affected", + "usns": [ + { + "related_usns_plan": [ + { + "additional_data": { + "associated_cves": [ + "CVE-2023-1076", + "CVE-2023-1118", + "CVE-2023-25012", + "CVE-2023-1855", + "CVE-2023-1990", + "CVE-2023-28866", + "CVE-2023-1998", + "CVE-2023-1077", + "CVE-2023-1583", + "CVE-2023-1670", + "CVE-2023-1032", + "CVE-2023-1079", + "CVE-2023-30456", + "CVE-2023-28466", + "CVE-2023-1989", + "CVE-2023-1829", + "CVE-2022-4269" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel (OEM) vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6033-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-32233", + "CVE-2023-2612" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel (OEM) vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6122-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-30456", + "CVE-2023-2612", + "CVE-2023-1670", + "CVE-2023-26606", + "CVE-2023-32233" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel (OEM) vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6123-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-30456", + "CVE-2023-32233", + "CVE-2023-2612", + "CVE-2022-4139", + "CVE-2022-3586", + "CVE-2023-1670" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel (OEM) vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6124-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-32233", + "CVE-2023-1380", + "CVE-2023-2612", + "CVE-2023-31436", + "CVE-2023-30456" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6127-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-30456", + "CVE-2023-1380", + "CVE-2023-32233", + "CVE-2023-2612", + "CVE-2023-31436" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6131-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-0459", + "CVE-2023-30456", + "CVE-2023-1380", + "CVE-2023-1075", + "CVE-2023-2162", + "CVE-2023-32233", + "CVE-2023-2612", + "CVE-2022-3707", + "CVE-2023-1118", + "CVE-2023-1513", + "CVE-2023-32269", + "CVE-2023-31436", + "CVE-2023-1078" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6132-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-32233", + "CVE-2023-1380", + "CVE-2023-2612", + "CVE-2023-31436", + "CVE-2023-30456" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel (Azure CVM) vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6135-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-32233", + "CVE-2023-28328", + "CVE-2023-1073", + "CVE-2023-30456", + "CVE-2023-1380", + "CVE-2023-31436" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6149-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-30456", + "CVE-2023-1380", + "CVE-2023-32233", + "CVE-2023-2612", + "CVE-2023-31436" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6150-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-31436", + "CVE-2023-2612", + "CVE-2023-30456", + "CVE-2023-1380", + "CVE-2023-32233" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel (Intel IoTG) vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6162-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-32254", + "CVE-2023-2002", + "CVE-2023-2156", + "CVE-2023-32250", + "CVE-2023-2269", + "CVE-2023-1380", + "CVE-2023-31436" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel (OEM) vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6173-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-33203", + "CVE-2023-1859", + "CVE-2023-1855", + "CVE-2023-33288", + "CVE-2023-2194", + "CVE-2023-30456", + "CVE-2023-32233", + "CVE-2023-2235", + "CVE-2023-2612", + "CVE-2023-28466", + "CVE-2023-1380", + "CVE-2023-1611", + "CVE-2023-1990", + "CVE-2023-31436", + "CVE-2023-1989", + "CVE-2023-1583", + "CVE-2023-28866", + "CVE-2023-30772", + "CVE-2023-1670", + "CVE-2022-4269" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6175-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-33203", + "CVE-2023-1859", + "CVE-2023-1855", + "CVE-2023-33288", + "CVE-2023-2194", + "CVE-2023-30456", + "CVE-2023-32233", + "CVE-2023-2235", + "CVE-2023-2612", + "CVE-2023-28466", + "CVE-2023-1380", + "CVE-2023-1611", + "CVE-2023-1990", + "CVE-2023-31436", + "CVE-2023-1989", + "CVE-2023-1583", + "CVE-2023-28866", + "CVE-2023-30772", + "CVE-2023-1670", + "CVE-2022-4269" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6186-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-1380", + "CVE-2023-32233", + "CVE-2022-4129", + "CVE-2023-2162", + "CVE-2023-26545", + "CVE-2022-3108", + "CVE-2023-1670", + "CVE-2023-1998", + "CVE-2022-3707", + "CVE-2023-1281", + "CVE-2023-1118", + "CVE-2023-30456", + "CVE-2023-0459", + "CVE-2023-2985", + "CVE-2023-1074", + "CVE-2023-2612", + "CVE-2023-1859", + "CVE-2023-32269", + "CVE-2023-1076", + "CVE-2022-3903", + "CVE-2023-1073", + "CVE-2023-1079", + "CVE-2023-0458", + "CVE-2023-1829", + "CVE-2023-1078", + "CVE-2023-3161", + "CVE-2023-25012", + "CVE-2023-1075", + "CVE-2023-1513", + "CVE-2023-1077", + "CVE-2023-31436" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel (Xilinx ZynqMP) vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6222-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-2162", + "CVE-2022-3707", + "CVE-2023-1078", + "CVE-2022-4129", + "CVE-2023-0459", + "CVE-2023-1859", + "CVE-2023-1077", + "CVE-2023-1079", + "CVE-2023-32269", + "CVE-2023-0458", + "CVE-2022-3903", + "CVE-2023-3161", + "CVE-2023-25012", + "CVE-2023-30456", + "CVE-2023-35788", + "CVE-2023-2612", + "CVE-2023-1829", + "CVE-2023-32233", + "CVE-2023-31436", + "CVE-2023-1380", + "CVE-2023-26545", + "CVE-2023-1075", + "CVE-2023-1998", + "CVE-2022-3108", + "CVE-2023-1513", + "CVE-2023-1074", + "CVE-2023-1073", + "CVE-2023-1281", + "CVE-2023-1670", + "CVE-2023-2985", + "CVE-2023-1118", + "CVE-2023-1076" + ], + "associated_launchpad_bugs": [ + "https://launchpad.net/bugs/2023220", + "https://launchpad.net/bugs/2023577" + ] + }, + "affected_packages": [], + "description": "Linux kernel (IoT) vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6256-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-3141", + "CVE-2023-40283", + "CVE-2023-28328", + "CVE-2023-3220", + "CVE-2023-1206", + "CVE-2023-1075", + "CVE-2023-4273", + "CVE-2023-4015", + "CVE-2022-27672", + "CVE-2023-1076", + "CVE-2023-28466", + "CVE-2023-3609", + "CVE-2023-4128", + "CVE-2023-2898", + "CVE-2023-3090", + "CVE-2023-2235", + "CVE-2023-2002", + "CVE-2023-32269", + "CVE-2023-3863", + "CVE-2023-31436", + "CVE-2023-2163", + "CVE-2023-3777", + "CVE-2023-3390", + "CVE-2023-3611", + "CVE-2023-3776", + "CVE-2023-0458", + "CVE-2023-4004", + "CVE-2023-4194", + "CVE-2022-4269", + "CVE-2023-20593", + "CVE-2023-1611", + "CVE-2023-2269", + "CVE-2023-1380", + "CVE-2023-3995", + "CVE-2023-2162", + "CVE-2023-3610", + "CVE-2023-4569" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel (OEM) vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6385-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2023-42755", + "CVE-2023-1380", + "CVE-2023-42752", + "CVE-2023-35001", + "CVE-2023-1206", + "CVE-2023-4623", + "CVE-2023-31436" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6460-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2024-24855", + "CVE-2023-30456", + "CVE-2023-4921" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6699-1", + "warnings": [] + } + ], + "target_usn_plan": { + "additional_data": { + "associated_cves": [ + "CVE-2023-30456", + "CVE-2023-1380", + "CVE-2023-32233", + "CVE-2023-31436" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "Linux kernel vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-6130-1", + "warnings": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixPlan" + } + """ + When I run `pro api u.pro.security.fix.usn.plan.v1 --data '{"usns": ["USN-4539-1", "USN-4038-1"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `usn_fix_plan` schema + Then API data field output matches regexp: + """ + { + "attributes": { + "usns_data": { + "expected_status": "fixed", + "usns": [ + { + "related_usns_plan": [], + "target_usn_plan": { + "additional_data": { + "associated_cves": [ + "CVE-2020-11728" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "AWL vulnerability", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-4539-1", + "warnings": [] + } + }, + { + "related_usns_plan": [ + { + "additional_data": { + "associated_cves": [ + "CVE-2016-3189", + "CVE-2019-12900" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "bzip2 vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-4038-2", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2019-12625", + "CVE-2019-12900" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "ClamAV vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-4146-1", + "warnings": [] + }, + { + "additional_data": { + "associated_cves": [ + "CVE-2019-12625", + "CVE-2019-12900" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [], + "description": "ClamAV vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "USN-4146-2", + "warnings": [] + } + ], + "target_usn_plan": { + "additional_data": { + "associated_cves": [ + "CVE-2016-3189", + "CVE-2019-12900" + ], + "associated_launchpad_bugs": [] + }, + "affected_packages": [ + "bzip2" + ], + "description": "bzip2 vulnerabilities", + "error": null, + "expected_status": "fixed", + "plan": [ + { + "data": { + "binary_packages": [ + "bzip2", + "libbz2-1.0" + ], + "pocket": "standard-updates", + "source_packages": [ + "bzip2" + ] + }, + "operation": "apt-upgrade", + "order": 1 + } + ], + "title": "USN-4038-1", + "warnings": [] + } + } + ] + } + }, + "meta": { + "environment_vars": [] + }, + "type": "USNFixPlan" + } + """ + + Examples: ubuntu release details + | release | machine_type | + | bionic | lxd-container | + + Scenario Outline: Fix command on an unattached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro api u.pro.security.fix.cve.plan.v1 --data '{"cves": ["CVE-2022-40982"]}'` as non-root + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `cve_fix_plan` schema + And API data field output matches regexp: + """ + { + "attributes": { + "cves_data": { + "cves": [ + { + "additional_data": {}, + "affected_packages": [], + "description": "Linux kernel (BlueField) vulnerabilities", + "error": null, + "expected_status": "not-affected", + "plan": [ + { + "data": { + "status": "system-not-affected" + }, + "operation": "no-op", + "order": 1 + } + ], + "title": "CVE-2022-40982", + "warnings": [] + } + ], + "expected_status": "not-affected" + } + }, + "meta": { + "environment_vars": [] + }, + "type": "CVEFixPlan" + } + """ + + Examples: ubuntu release details + | release | machine_type | + | mantic | lxd-vm | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/api_full_auto_attach.feature ubuntu-advantage-tools-32~16.04/features/api_full_auto_attach.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/api_full_auto_attach.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/api_full_auto_attach.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,36 +1,38 @@ Feature: Full Auto-Attach Endpoint - Scenario Outline: Run auto-attach on cloud instance. - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - data_dir: /var/lib/ubuntu-advantage - log_level: debug - log_file: /var/log/ubuntu-advantage.log - """ - When I create the file `/tmp/full_auto_attach.py` with the following: - """ - from uaclient.api.u.pro.attach.auto.full_auto_attach.v1 import full_auto_attach, FullAutoAttachOptions + Scenario Outline: Run auto-attach on cloud instance. + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + data_dir: /var/lib/ubuntu-advantage + log_level: debug + log_file: /var/log/ubuntu-advantage.log + """ + When I create the file `/tmp/full_auto_attach.py` with the following: + """ + from uaclient.api.u.pro.attach.auto.full_auto_attach.v1 import full_auto_attach, FullAutoAttachOptions - full_auto_attach(FullAutoAttachOptions(enable=["esm-infra"])) - """ - And I run `python3 /tmp/full_auto_attach.py` with sudo - Then I verify that `esm-infra` is enabled - And I verify that `livepatch` is disabled + full_auto_attach(FullAutoAttachOptions(enable=["esm-infra"])) + """ + And I run `python3 /tmp/full_auto_attach.py` with sudo + Then I verify that `esm-infra` is enabled + And I verify that `livepatch` is disabled - Examples: - | release | machine_type | - | xenial | aws.pro | - | xenial | azure.pro | - | xenial | gcp.pro | - | bionic | aws.pro | - | bionic | azure.pro | - | bionic | gcp.pro | - | focal | aws.pro | - | focal | azure.pro | - | focal | gcp.pro | - | jammy | aws.pro | - | jammy | azure.pro | - | jammy | gcp.pro | - + Examples: + | release | machine_type | + | xenial | aws.pro | + | xenial | azure.pro | + | xenial | gcp.pro | + | bionic | aws.pro | + | bionic | azure.pro | + | bionic | gcp.pro | + | focal | aws.pro | + | focal | azure.pro | + | focal | gcp.pro | + | jammy | aws.pro | + | jammy | azure.pro | + | jammy | gcp.pro | + | noble | aws.pro | + | noble | azure.pro | + | noble | gcp.pro | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/api_full_token_attach.feature ubuntu-advantage-tools-32~16.04/features/api_full_token_attach.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/api_full_token_attach.feature 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/api_full_token_attach.feature 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,143 @@ +Feature: Attach API endpoint + + Scenario Outline: Attach API endpoint + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify that running `pro api u.pro.attach.token.full_token_attach.v1 --args token=TOKEN` `as non-root` exits `1` + Then stdout is a json matching the `api_response` schema + And API full output matches regexp: + """ + { + "_schema_version": "v1", + "data": { + "meta": { + "environment_vars": [] + } + }, + "errors": [ + { + "code": "nonroot-user", + "meta": {}, + "title": "This command must be run as root (try using sudo)." + } + ], + "result": "failure", + "version": ".*", + "warnings": [] + } + """ + When I verify that running `pro api u.pro.attach.token.full_token_attach.v1 --args token=TOKEN` `with sudo` exits `1` + Then stdout is a json matching the `api_response` schema + And API full output matches regexp: + """ + { + "_schema_version": "v1", + "data": { + "meta": { + "environment_vars": [] + } + }, + "errors": [ + { + "code": "attach-invalid-token", + "meta": {}, + "title": "Invalid token. See https://ubuntu.com/pro/dashboard" + } + ], + "result": "failure", + "version": ".*", + "warnings": [] + } + """ + When I attach using the API + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `attach` schema + And API full output matches regexp: + """ + { + "_schema_version": "v1", + "data": { + "attributes": { + "enabled": [ + "esm-apps", + "esm-infra" + ], + "reboot_required": false + }, + "meta": { + "environment_vars": [] + }, + "type": "FullTokenAttach" + }, + "errors": [], + "result": "success", + "version": ".*", + "warnings": [] + } + """ + And the machine is attached + And I verify that `esm-apps` is enabled + And I verify that `esm-infra` is enabled + When I run `pro detach --assume-yes` with sudo + And I attach using the API without enabling services + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `attach` schema + And API full output matches regexp: + """ + { + "_schema_version": "v1", + "data": { + "attributes": { + "enabled": [], + "reboot_required": false + }, + "meta": { + "environment_vars": [] + }, + "type": "FullTokenAttach" + }, + "errors": [], + "result": "success", + "version": ".*", + "warnings": [] + } + """ + And the machine is attached + And I verify that `esm-apps` is disabled + And I verify that `esm-infra` is disabled + When I run `pro detach --assume-yes` with sudo + And I attach using the API and stdin + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `attach` schema + And API full output matches regexp: + """ + { + "_schema_version": "v1", + "data": { + "attributes": { + "enabled": [ + "esm-apps", + "esm-infra" + ], + "reboot_required": false + }, + "meta": { + "environment_vars": [] + }, + "type": "FullTokenAttach" + }, + "errors": [], + "result": "success", + "version": ".*", + "warnings": [] + } + """ + And the machine is attached + And I verify that `esm-apps` is enabled + And I verify that `esm-infra` is enabled + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/api_magic_attach.feature ubuntu-advantage-tools-32~16.04/features/api_magic_attach.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/api_magic_attach.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/api_magic_attach.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,72 +1,130 @@ Feature: Magic Attach endpoints - Scenario Outline: Call magic attach endpoints - Given a `` `` machine with ubuntu-advantage-tools installed - When I change contract to staging with sudo - And I verify that running `pro api u.pro.attach.magic.revoke.v1` `as non-root` exits `1` - Then stdout is a json matching the `api_response` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"meta": {\"environment_vars\": \[]}}, "errors": \[{"code": "api-missing-argument", "meta": {\"arg\": \"magic_token\", \"endpoint\": \"u.pro.attach.magic.revoke.v1\"}, "title": "Missing argument \'magic_token\' for endpoint u.pro.attach.magic.revoke.v1"}\], "result": "failure", "version": ".*", "warnings": \[\]} - """ - When I verify that running `pro api u.pro.attach.magic.wait.v1 --args magic_token=INVALID` `as non-root` exits `1` - Then stdout is a json matching the `api_response` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"meta": {"environment_vars": \[\]}}, "errors": \[{"code": "magic-attach-token-error", "meta": {}, "title": "The magic attach token is invalid, has expired or never existed"}\], "result": "failure", "version": ".*", "warnings": \[\]} - """ - When I verify that running `pro api u.pro.attach.magic.revoke.v1 --args magic_token=INVALID` `as non-root` exits `1` - Then stdout is a json matching the `api_response` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"meta": {"environment_vars": \[\]}}, "errors": \[{"code": "magic-attach-token-error", "meta": {}, "title": "The magic attach token is invalid, has expired or never existed"}\], "result": "failure", "version": ".*", "warnings": \[\]} - """ - When I initiate the magic attach flow - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `magic_attach` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"expires": ".*", "expires_in": .*, "token": ".*", "user_code": ".*"}, "meta": {\"environment_vars\": \[]}, "type": "MagicAttachInitiate"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I create the file `/tmp/response-overlay.json` with the following: - """ + Scenario Outline: Call magic attach endpoints + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify that running `pro api u.pro.attach.magic.revoke.v1` `as non-root` exits `1` + Then stdout is a json matching the `api_response` schema + And API errors field output matches regexp: + """ + [ { - "https://contracts.staging.canonical.com/v1/magic-attach": [ - { - "code": 200, - "response": { - "userCode": "123", - "token": "testToken", - "expires": "expire-date", - "expiresIn": 2000, - "contractID": "test-contract-id", - "contractToken": "contract-token" - } - }] + "code": "api-missing-argument", + "meta": { + "arg": "magic_token", + "endpoint": "u.pro.attach.magic.revoke.v1" + }, + "title": "Missing argument 'magic_token' for endpoint u.pro.attach.magic.revoke.v1" } - """ - And I append the following on uaclient config: - """ - features: - serviceclient_url_responses: "/tmp/response-overlay.json" - """ - And I wait for the magic attach token to be activated - Then stdout is a json matching the `api_response` schema - And the json API response data matches the `magic_attach` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"contract_id": "test-contract-id", "contract_token": "contract-token", "expires": "expire-date", "expires_in": 2000, "token": "testToken", "user_code": "123"}, "meta": {\"environment_vars\": \[]}, "type": "MagicAttachWait"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I revoke the magic attach token - Then stdout is a json matching the `api_response` schema - And stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {}, "meta": {\"environment_vars\": \[]}, "type": "MagicAttachRevoke"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ + ] + """ + When I verify that running `pro api u.pro.attach.magic.wait.v1 --args magic_token=INVALID` `as non-root` exits `1` + Then stdout is a json matching the `api_response` schema + And API errors field output matches regexp: + """ + [ + { + "code": "magic-attach-token-error", + "meta": {}, + "title": "The magic attach token is invalid, has expired or never existed" + } + ] + """ + When I verify that running `pro api u.pro.attach.magic.revoke.v1 --args magic_token=INVALID` `as non-root` exits `1` + Then stdout is a json matching the `api_response` schema + And API errors field output matches regexp: + """ + [ + { + "code": "magic-attach-token-error", + "meta": {}, + "title": "The magic attach token is invalid, has expired or never existed" + } + ] + """ + When I initiate the magic attach flow + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `magic_attach` schema + And API data field output matches regexp: + """ + { + "attributes": { + "expires": ".*", + "expires_in": .*, + "token": ".*", + "user_code": ".*" + }, + "meta": { + "environment_vars": [] + }, + "type": "MagicAttachInitiate" + } + """ + When I create the file `/tmp/response-overlay.json` with the following: + """ + { + "https://contracts.canonical.com/v1/magic-attach": [ + { + "code": 200, + "response": { + "userCode": "123", + "token": "testToken", + "expires": "expire-date", + "expiresIn": 2000, + "contractID": "test-contract-id", + "contractToken": "contract-token" + } + }] + } + """ + And I append the following on uaclient config: + """ + features: + serviceclient_url_responses: "/tmp/response-overlay.json" + """ + And I wait for the magic attach token to be activated + Then stdout is a json matching the `api_response` schema + And the json API response data matches the `magic_attach` schema + And API data field output matches regexp: + """ + { + "attributes": { + "contract_id": "test-contract-id", + "contract_token": "contract-token", + "expires": "expire-date", + "expires_in": 2000, + "token": "testToken", + "user_code": "123" + }, + "meta": { + "environment_vars": [] + }, + "type": "MagicAttachWait" + } + """ + When I revoke the magic attach token + Then stdout is a json matching the `api_response` schema + And API full output matches regexp: + """ + { + "_schema_version": "v1", + "data": { + "attributes": {}, + "meta": { + "environment_vars": [] + }, + "type": "MagicAttachRevoke" + }, + "errors": [], + "result": "success", + "version": ".*", + "warnings": [] + } + """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/api_packages.feature ubuntu-advantage-tools-32~16.04/features/api_packages.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/api_packages.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/api_packages.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,36 +1,57 @@ Feature: Package related API endpoints - @uses.config.contract_token - Scenario Outline: Call packages API endpoints to see information in a Ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro api u.pro.packages.summary.v1` as non-root - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"summary": {"num_esm_apps_packages": \d+, "num_esm_infra_packages": \d+, "num_installed_packages": \d+, "num_main_packages": \d+, "num_multiverse_packages": \d+, "num_restricted_packages": \d+, "num_third_party_packages": \d+, "num_universe_packages": \d+, "num_unknown_packages": \d+}}, "meta": {"environment_vars": \[\]}, "type": "PackageSummary"}, "errors": \[\], "result": "success", "version": ".+", "warnings": \[\]} - """ - When I run `pro api u.pro.packages.updates.v1` as non-root - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"summary": {"num_esm_apps_updates": \d+, "num_esm_infra_updates": \d+, "num_standard_security_updates": \d+, "num_standard_updates": \d+, "num_updates": \d+}, "updates": \[.*\]}, "meta": {"environment_vars": \[\]}, "type": "PackageUpdates"}, "errors": \[\], "result": "success", "version": ".+", "warnings": \[\]} - """ - # Make sure we have an updated system - When I attach `contract_token` with sudo - And I apt upgrade - # Install some outdated package - And I apt install `=` - # See the update there - When I store candidate version of package `` - And I regexify `candidate` stored var - And I run `pro api u.pro.packages.updates.v1` as non-root - Then stdout matches regexp: - """ - {"download_size": \d+, "origin": ".+", "package": "", "provided_by": "", "status": "upgrade_available", "version": "$behave_var{stored_var candidate}"} - """ + @uses.config.contract_token + Scenario Outline: Call packages API endpoints to see information in a Ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro api u.pro.packages.summary.v1` as non-root + Then API data field output matches regexp: + """ + { + "attributes": { + "summary": { + "num_esm_apps_packages": \d+, + "num_esm_infra_packages": \d+, + "num_installed_packages": \d+, + "num_main_packages": \d+, + "num_multiverse_packages": \d+, + "num_restricted_packages": \d+, + "num_third_party_packages": \d+, + "num_universe_packages": \d+, + "num_unknown_packages": \d+ + } + }, + "meta": { + "environment_vars": [] + }, + "type": "PackageSummary" + } + """ + When I run `pro api u.pro.packages.updates.v1` as non-root + Then stdout matches regexp: + """ + {"_schema_version": "v1", "data": {"attributes": {"summary": {"num_esm_apps_updates": \d+, "num_esm_infra_updates": \d+, "num_standard_security_updates": \d+, "num_standard_updates": \d+, "num_updates": \d+}, "updates": \[.*\]}, "meta": {"environment_vars": \[\]}, "type": "PackageUpdates"}, "errors": \[\], "result": "success", "version": ".+", "warnings": \[\]} + """ + # Make sure we have an updated system + When I attach `contract_token` with sudo + And I apt upgrade + # Install some outdated package + And I apt install `=` + # See the update there + When I store candidate version of package `` + And I regexify `candidate` stored var + And I run `pro api u.pro.packages.updates.v1` as non-root + Then stdout matches regexp: + """ + {"download_size": \d+, "origin": ".+", "package": "", "provided_by": "", "status": "upgrade_available", "version": "$behave_var{stored_var candidate}"} + """ - Examples: ubuntu release - | release | machine_type | package | outdated_version | provided_by | - | xenial | lxd-container | libcurl3-gnutls | 7.47.0-1ubuntu2 | esm-infra | - | bionic | lxd-container | libcurl4 | 7.58.0-2ubuntu3 | esm-infra | - | focal | lxd-container | libcurl4 | 7.68.0-1ubuntu2 | standard-security | - | jammy | lxd-container | libcurl4 | 7.81.0-1 | standard-security | - | mantic | lxd-container | libcurl4 | 8.2.1-1ubuntu3 | standard-security | + Examples: ubuntu release + | release | machine_type | package | outdated_version | provided_by | + | xenial | lxd-container | libcurl3-gnutls | 7.47.0-1ubuntu2 | esm-infra | + | bionic | lxd-container | libcurl4 | 7.58.0-2ubuntu3 | esm-infra | + | bionic | wsl | libcurl4 | 7.58.0-2ubuntu3 | esm-infra | + | focal | lxd-container | libcurl4 | 7.68.0-1ubuntu2 | standard-security | + | focal | wsl | libcurl4 | 7.68.0-1ubuntu2 | standard-security | + | jammy | lxd-container | libcurl4 | 7.81.0-1 | standard-security | + | jammy | wsl | libcurl4 | 7.81.0-1 | standard-security | + | mantic | lxd-container | libcurl4 | 8.2.1-1ubuntu3 | standard-security | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/api_security.feature ubuntu-advantage-tools-32~16.04/features/api_security.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/api_security.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/api_security.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,71 +1,71 @@ Feature: API security/security status tests - @uses.config.contract_token - Scenario: Call Livepatched CVEs endpoint - Given a `xenial` `lxd-vm` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `pro api u.pro.security.status.livepatch_cves.v1` as non-root - Then stdout matches regexp: - """ - {"name": "cve-2013-1798", "patched": true} - """ - And stdout matches regexp: - """ - "type": "LivepatchCVEs" - """ + @uses.config.contract_token + Scenario: Call Livepatched CVEs endpoint + Given a `xenial` `lxd-vm` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `pro api u.pro.security.status.livepatch_cves.v1` as non-root + Then stdout matches regexp: + """ + {"name": "cve-2013-1798", "patched": true} + """ + And stdout matches regexp: + """ + "type": "LivepatchCVEs" + """ - @uses.config.contract_token - Scenario Outline: Call package manifest endpoint for machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that `esm-infra` is enabled - When I apt update - And I apt upgrade - And I apt install `jq bzip2` - # Install the oscap version 1.3.7 which solved the epoch error message issue - And I apt install `cmake libdbus-1-dev libdbus-glib-1-dev libcurl4-openssl-dev libgcrypt20-dev libselinux1-dev libxslt1-dev libgconf2-dev libacl1-dev libblkid-dev libcap-dev libxml2-dev libldap2-dev libpcre3-dev swig libxml-parser-perl libxml-xpath-perl libperl-dev libbz2-dev g++ libapt-pkg-dev libyaml-dev libxmlsec1-dev libxmlsec1-openssl` - And I run `wget https://github.com/OpenSCAP/openscap/releases/download/1.3.7/openscap-1.3.7.tar.gz` as non-root - And I run `tar xzf openscap-1.3.7.tar.gz` as non-root - And I run shell command `mkdir -p openscap-1.3.7/build` as non-root - And I run shell command `cd openscap-1.3.7/build/ && cmake ..` with sudo - And I run shell command `cd openscap-1.3.7/build/ && make` with sudo - And I run shell command `cd openscap-1.3.7/build/ && make install` with sudo - # Installs its shared libs in /usr/local/lib/ - And I run `ldconfig` with sudo - And I run shell command `pro api u.security.package_manifest.v1 | jq -r '.data.attributes.manifest_data' > manifest` as non-root - And I run shell command `wget https://security-metadata.canonical.com/oval/oci.com.ubuntu..usn.oval.xml.bz2` as non-root - And I run `bunzip2 oci.com.ubuntu..usn.oval.xml.bz2` as non-root - And I run shell command `oscap oval eval --report report.html oci.com.ubuntu..usn.oval.xml` as non-root - Then stdout matches regexp: - """ - oval:com.ubuntu.:def::\s+false - """ - # Trigger CVE https://ubuntu.com/security/CVE-2018-10846 with ID 39991000000 in OVAL data ( == Xenial $ Bionic) - # Trigger CVE https://ubuntu.com/security/CVE-2022-2509 with ID 55501000000 in OVAL data ( > Xenial) - When I run shell command `sed -i -E 's/libgnutls30:amd64\s+.*/libgnutls30:amd64 /' manifest` as non-root - And I run shell command `oscap oval eval --report report.html oci.com.ubuntu..usn.oval.xml` as non-root - Then stdout matches regexp: - """ - oval:com.ubuntu.:def::\s+true - """ - # Update the manifest - When I run shell command `pro api u.security.package_manifest.v1 | jq -r '.data.attributes.manifest_data' > manifest` as non-root - And I run shell command `oscap oval eval --report report.html oci.com.ubuntu..usn.oval.xml` as non-root - Then stdout matches regexp: - """ - oval:com.ubuntu.:def::\s+false - """ - # Downgrade the package - When I apt install `libgnutls30=` - And I run shell command `pro api u.security.package_manifest.v1 | jq -r '.data.attributes.manifest_data' > manifest` as non-root - And I run shell command `oscap oval eval --report report.html oci.com.ubuntu..usn.oval.xml` as non-root - Then stdout matches regexp: - """ - oval:com.ubuntu.:def::\s+true - """ - Examples: ubuntu release - | release | machine_type | base_version | CVE_ID | - | xenial | lxd-container | 3.4.10-4ubuntu1 | 39991000000 | - | bionic | lxd-container | 3.5.18-1ubuntu1 | 55501000000 | - | focal | lxd-container | 3.6.13-2ubuntu1 | 55501000000 | - | jammy | lxd-container | 3.7.3-4ubuntu1 | 55501000000 | + @uses.config.contract_token + Scenario Outline: Call package manifest endpoint for machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that `esm-infra` is enabled + When I apt upgrade + And I apt install `jq bzip2` + # Install the oscap version 1.3.7 which solved the epoch error message issue + And I apt install `cmake libdbus-1-dev libdbus-glib-1-dev libcurl4-openssl-dev libgcrypt20-dev libselinux1-dev libxslt1-dev libgconf2-dev libacl1-dev libblkid-dev libcap-dev libxml2-dev libldap2-dev libpcre3-dev swig libxml-parser-perl libxml-xpath-perl libperl-dev libbz2-dev g++ libapt-pkg-dev libyaml-dev libxmlsec1-dev libxmlsec1-openssl` + And I run `wget https://github.com/OpenSCAP/openscap/releases/download/1.3.7/openscap-1.3.7.tar.gz` as non-root + And I run `tar xzf openscap-1.3.7.tar.gz` as non-root + And I run shell command `mkdir -p openscap-1.3.7/build` as non-root + And I run shell command `cd openscap-1.3.7/build/ && cmake ..` with sudo + And I run shell command `cd openscap-1.3.7/build/ && make` with sudo + And I run shell command `cd openscap-1.3.7/build/ && make install` with sudo + # Installs its shared libs in /usr/local/lib/ + And I run `ldconfig` with sudo + And I run shell command `pro api u.security.package_manifest.v1 | jq -r '.data.attributes.manifest_data' > manifest` as non-root + And I run shell command `wget https://security-metadata.canonical.com/oval/oci.com.ubuntu..usn.oval.xml.bz2` as non-root + And I run `bunzip2 oci.com.ubuntu..usn.oval.xml.bz2` as non-root + And I run shell command `oscap oval eval --report report.html oci.com.ubuntu..usn.oval.xml` as non-root + Then stdout matches regexp: + """ + oval:com.ubuntu.:def::\s+false + """ + # Trigger CVE https://ubuntu.com/security/CVE-2018-10846 with ID 39991000000 in OVAL data ( == Xenial $ Bionic) + # Trigger CVE https://ubuntu.com/security/CVE-2022-2509 with ID 55501000000 in OVAL data ( > Xenial) + When I run shell command `sed -i -E 's/libgnutls30:amd64\s+.*/libgnutls30:amd64 /' manifest` as non-root + And I run shell command `oscap oval eval --report report.html oci.com.ubuntu..usn.oval.xml` as non-root + Then stdout matches regexp: + """ + oval:com.ubuntu.:def::\s+true + """ + # Update the manifest + When I run shell command `pro api u.security.package_manifest.v1 | jq -r '.data.attributes.manifest_data' > manifest` as non-root + And I run shell command `oscap oval eval --report report.html oci.com.ubuntu..usn.oval.xml` as non-root + Then stdout matches regexp: + """ + oval:com.ubuntu.:def::\s+false + """ + # Downgrade the package + When I apt install `libgnutls30=` + And I run shell command `pro api u.security.package_manifest.v1 | jq -r '.data.attributes.manifest_data' > manifest` as non-root + And I run shell command `oscap oval eval --report report.html oci.com.ubuntu..usn.oval.xml` as non-root + Then stdout matches regexp: + """ + oval:com.ubuntu.:def::\s+true + """ + + Examples: ubuntu release + | release | machine_type | base_version | CVE_ID | + | xenial | lxd-container | 3.4.10-4ubuntu1 | 39991000000 | + | bionic | lxd-container | 3.5.18-1ubuntu1 | 55501000000 | + | focal | lxd-container | 3.6.13-2ubuntu1 | 55501000000 | + | jammy | lxd-container | 3.7.3-4ubuntu1 | 55501000000 | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/api_services_dependencies.feature ubuntu-advantage-tools-32~16.04/features/api_services_dependencies.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/api_services_dependencies.feature 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/api_services_dependencies.feature 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,373 @@ +Feature: u.pro.services.dependencies + + Scenario Outline: u.pro.services.dependencies.v1 + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro api u.pro.services.dependencies.v1` as non-root + Then API data field output is: + """ + { + "attributes": { + "services": [ + { + "depends_on": [], + "incompatible_with": [], + "name": "anbox-cloud" + }, + { + "depends_on": [], + "incompatible_with": [], + "name": "cc-eal" + }, + { + "depends_on": [], + "incompatible_with": [], + "name": "cis" + }, + { + "depends_on": [], + "incompatible_with": [], + "name": "esm-apps" + }, + { + "depends_on": [], + "incompatible_with": [], + "name": "esm-infra" + }, + { + "depends_on": [], + "incompatible_with": [ + { + "name": "livepatch", + "reason": { + "code": "livepatch-invalidates-fips", + "title": "Livepatch cannot be enabled while running the official FIPS certified kernel. If you would like a FIPS compliant kernel with additional bug fixes and security updates, you can use the FIPS Updates service with Livepatch." + } + }, + { + "name": "fips-updates", + "reason": { + "code": "fips-updates-invalidates-fips", + "title": "FIPS cannot be enabled if FIPS Updates has ever been enabled because FIPS Updates installs security patches that aren't officially certified." + } + }, + { + "name": "realtime-kernel", + "reason": { + "code": "realtime-fips-incompatible", + "title": "Realtime and FIPS require different kernels, so you cannot enable both at the same time." + } + } + ], + "name": "fips" + }, + { + "depends_on": [], + "incompatible_with": [ + { + "name": "fips", + "reason": { + "code": "fips-invalidates-fips-updates", + "title": "FIPS Updates cannot be enabled if FIPS is enabled. FIPS Updates installs security patches that aren't officially certified." + } + }, + { + "name": "realtime-kernel", + "reason": { + "code": "realtime-fips-updates-incompatible", + "title": "Realtime and FIPS Updates require different kernels, so you cannot enable both at the same time." + } + } + ], + "name": "fips-updates" + }, + { + "depends_on": [], + "incompatible_with": [ + { + "name": "livepatch", + "reason": { + "code": "livepatch-invalidates-fips", + "title": "Livepatch cannot be enabled while running the official FIPS certified kernel. If you would like a FIPS compliant kernel with additional bug fixes and security updates, you can use the FIPS Updates service with Livepatch." + } + }, + { + "name": "fips-updates", + "reason": { + "code": "fips-updates-invalidates-fips", + "title": "FIPS cannot be enabled if FIPS Updates has ever been enabled because FIPS Updates installs security patches that aren't officially certified." + } + }, + { + "name": "realtime-kernel", + "reason": { + "code": "realtime-fips-incompatible", + "title": "Realtime and FIPS require different kernels, so you cannot enable both at the same time." + } + }, + { + "name": "fips", + "reason": { + "code": "fips-invalidates-fips-updates", + "title": "FIPS Updates cannot be enabled if FIPS is enabled. FIPS Updates installs security patches that aren't officially certified." + } + } + ], + "name": "fips-preview" + }, + { + "depends_on": [], + "incompatible_with": [], + "name": "landscape" + }, + { + "depends_on": [], + "incompatible_with": [ + { + "name": "fips", + "reason": { + "code": "livepatch-invalidates-fips", + "title": "Livepatch cannot be enabled while running the official FIPS certified kernel. If you would like a FIPS compliant kernel with additional bug fixes and security updates, you can use the FIPS Updates service with Livepatch." + } + }, + { + "name": "realtime-kernel", + "reason": { + "code": "realtime-livepatch-incompatible", + "title": "Livepatch is not currently supported for the Real-time kernel." + } + } + ], + "name": "livepatch" + }, + { + "depends_on": [], + "incompatible_with": [ + { + "name": "fips", + "reason": { + "code": "realtime-fips-incompatible", + "title": "Realtime and FIPS require different kernels, so you cannot enable both at the same time." + } + }, + { + "name": "fips-updates", + "reason": { + "code": "realtime-fips-updates-incompatible", + "title": "Realtime and FIPS Updates require different kernels, so you cannot enable both at the same time." + } + }, + { + "name": "livepatch", + "reason": { + "code": "realtime-livepatch-incompatible", + "title": "Livepatch is not currently supported for the Real-time kernel." + } + } + ], + "name": "realtime-kernel" + }, + { + "depends_on": [ + { + "name": "esm-infra", + "reason": { + "code": "ros-requires-esm", + "title": "ROS packages assume ESM updates are enabled." + } + }, + { + "name": "esm-apps", + "reason": { + "code": "ros-requires-esm", + "title": "ROS packages assume ESM updates are enabled." + } + } + ], + "incompatible_with": [], + "name": "ros" + }, + { + "depends_on": [ + { + "name": "esm-infra", + "reason": { + "code": "ros-requires-esm", + "title": "ROS packages assume ESM updates are enabled." + } + }, + { + "name": "esm-apps", + "reason": { + "code": "ros-requires-esm", + "title": "ROS packages assume ESM updates are enabled." + } + }, + { + "name": "ros", + "reason": { + "code": "ros-updates-requires-ros", + "title": "ROS bug-fix updates assume ROS security fix updates are enabled." + } + } + ], + "incompatible_with": [], + "name": "ros-updates" + } + ] + }, + "meta": { + "environment_vars": [] + }, + "type": "ServiceDependencies" + } + """ + When I create the file `/tmp/dependencies.py` with the following: + """ + import yaml + from uaclient.api.u.pro.services.dependencies.v1 import dependencies + + print(yaml.dump(dependencies().to_dict(), default_flow_style=False)) + """ + And I run `python3 /tmp/dependencies.py` with sudo + Then I will see the following on stdout: + """ + services: + - depends_on: [] + incompatible_with: [] + name: anbox-cloud + - depends_on: [] + incompatible_with: [] + name: cc-eal + - depends_on: [] + incompatible_with: [] + name: cis + - depends_on: [] + incompatible_with: [] + name: esm-apps + - depends_on: [] + incompatible_with: [] + name: esm-infra + - depends_on: [] + incompatible_with: + - name: livepatch + reason: + code: livepatch-invalidates-fips + title: Livepatch cannot be enabled while running the official FIPS certified + kernel. If you would like a FIPS compliant kernel with additional bug fixes + and security updates, you can use the FIPS Updates service with Livepatch. + - name: fips-updates + reason: + code: fips-updates-invalidates-fips + title: FIPS cannot be enabled if FIPS Updates has ever been enabled because + FIPS Updates installs security patches that aren't officially certified. + - name: realtime-kernel + reason: + code: realtime-fips-incompatible + title: Realtime and FIPS require different kernels, so you cannot enable both + at the same time. + name: fips + - depends_on: [] + incompatible_with: + - name: fips + reason: + code: fips-invalidates-fips-updates + title: FIPS Updates cannot be enabled if FIPS is enabled. FIPS Updates installs + security patches that aren't officially certified. + - name: realtime-kernel + reason: + code: realtime-fips-updates-incompatible + title: Realtime and FIPS Updates require different kernels, so you cannot enable + both at the same time. + name: fips-updates + - depends_on: [] + incompatible_with: + - name: livepatch + reason: + code: livepatch-invalidates-fips + title: Livepatch cannot be enabled while running the official FIPS certified + kernel. If you would like a FIPS compliant kernel with additional bug fixes + and security updates, you can use the FIPS Updates service with Livepatch. + - name: fips-updates + reason: + code: fips-updates-invalidates-fips + title: FIPS cannot be enabled if FIPS Updates has ever been enabled because + FIPS Updates installs security patches that aren't officially certified. + - name: realtime-kernel + reason: + code: realtime-fips-incompatible + title: Realtime and FIPS require different kernels, so you cannot enable both + at the same time. + - name: fips + reason: + code: fips-invalidates-fips-updates + title: FIPS Updates cannot be enabled if FIPS is enabled. FIPS Updates installs + security patches that aren't officially certified. + name: fips-preview + - depends_on: [] + incompatible_with: [] + name: landscape + - depends_on: [] + incompatible_with: + - name: fips + reason: + code: livepatch-invalidates-fips + title: Livepatch cannot be enabled while running the official FIPS certified + kernel. If you would like a FIPS compliant kernel with additional bug fixes + and security updates, you can use the FIPS Updates service with Livepatch. + - name: realtime-kernel + reason: + code: realtime-livepatch-incompatible + title: Livepatch is not currently supported for the Real-time kernel. + name: livepatch + - depends_on: [] + incompatible_with: + - name: fips + reason: + code: realtime-fips-incompatible + title: Realtime and FIPS require different kernels, so you cannot enable both + at the same time. + - name: fips-updates + reason: + code: realtime-fips-updates-incompatible + title: Realtime and FIPS Updates require different kernels, so you cannot enable + both at the same time. + - name: livepatch + reason: + code: realtime-livepatch-incompatible + title: Livepatch is not currently supported for the Real-time kernel. + name: realtime-kernel + - depends_on: + - name: esm-infra + reason: + code: ros-requires-esm + title: ROS packages assume ESM updates are enabled. + - name: esm-apps + reason: + code: ros-requires-esm + title: ROS packages assume ESM updates are enabled. + incompatible_with: [] + name: ros + - depends_on: + - name: esm-infra + reason: + code: ros-requires-esm + title: ROS packages assume ESM updates are enabled. + - name: esm-apps + reason: + code: ros-requires-esm + title: ROS packages assume ESM updates are enabled. + - name: ros + reason: + code: ros-updates-requires-ros + title: ROS bug-fix updates assume ROS security fix updates are enabled. + incompatible_with: [] + name: ros-updates + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/api_unattended_upgrades.feature ubuntu-advantage-tools-32~16.04/features/api_unattended_upgrades.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/api_unattended_upgrades.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/api_unattended_upgrades.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,210 +1,256 @@ Feature: api.u.unattended_upgrades.status.v1 - Scenario Outline: v1 unattended upgrades status - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro api u.unattended_upgrades.status.v1` as non-root - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"apt_periodic_job_enabled": true, "package_lists_refresh_frequency_days": 1, "systemd_apt_timer_enabled": true, "unattended_upgrades_allowed_origins": \["\$\{distro_id\}:\$\{distro_codename\}", "\$\{distro_id\}:\$\{distro_codename\}\-security", "\$\{distro_id\}ESMApps:\$\{distro_codename\}\-apps-security", "\$\{distro_id\}ESM:\$\{distro_codename\}\-infra-security"\], "unattended_upgrades_disabled_reason": null, "unattended_upgrades_frequency_days": 1, "unattended_upgrades_last_run": null, "unattended_upgrades_running": true}, "meta": {"environment_vars": \[\], "raw_config": {"APT::Periodic::Enable": "1", "APT::Periodic::Unattended-Upgrade": "1", "APT::Periodic::Update-Package-Lists": "1", "Unattended-Upgrade::Allowed-Origins": \["\$\{distro_id\}:\$\{distro_codename\}", "\$\{distro_id\}:\$\{distro_codename\}\-security", "\$\{distro_id\}ESMApps:\$\{distro_codename\}\-apps-security", "\$\{distro_id\}ESM:\$\{distro_codename\}\-infra-security"\][,]?\s*}}, "type": "UnattendedUpgradesStatus"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I create the file `/etc/apt/apt.conf.d/99test` with the following: - """ - APT::Periodic::Enable "0"; - """ - And I apt update - And I apt install `jq` - And I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.apt_periodic_job_enabled` as non-root - Then I will see the following on stdout: - """ - false - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_running` as non-root - Then I will see the following on stdout: - """ - false - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.msg` as non-root - Then I will see the following on stdout: - """ - "APT::Periodic::Enable is turned off" - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.code` as non-root - Then I will see the following on stdout: - """ - "unattended-upgrades-cfg-value-turned-off" - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq '.data.meta.raw_config.\"APT::Periodic::Enable\"'` as non-root - Then I will see the following on stdout: - """ - "0" - """ - When I create the file `/etc/apt/apt.conf.d/99test` with the following: - """ - APT::Periodic::Update-Package-Lists "0"; - """ - And I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.apt_periodic_job_enabled` as non-root - Then I will see the following on stdout: - """ - true - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.package_lists_refresh_frequency_days` as non-root - Then I will see the following on stdout: - """ - 0 - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_running` as non-root - Then I will see the following on stdout: - """ - false - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.msg` as non-root - Then I will see the following on stdout: - """ - "APT::Periodic::Update-Package-Lists is turned off" - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.code` as non-root - Then I will see the following on stdout: - """ - "unattended-upgrades-cfg-value-turned-off" - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq '.data.meta.raw_config.\"APT::Periodic::Update-Package-Lists\"'` as non-root - Then I will see the following on stdout: - """ - "0" - """ - When I create the file `/etc/apt/apt.conf.d/99test` with the following: - """ - APT::Periodic::Unattended-Upgrade "0"; - """ - And I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_frequency_days` as non-root - Then I will see the following on stdout: - """ - 0 - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.package_lists_refresh_frequency_days` as non-root - Then I will see the following on stdout: - """ - 1 - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_running` as non-root - Then I will see the following on stdout: - """ - false - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.msg` as non-root - Then I will see the following on stdout: - """ - "APT::Periodic::Unattended-Upgrade is turned off" - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.code` as non-root - Then I will see the following on stdout: - """ - "unattended-upgrades-cfg-value-turned-off" - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq '.data.meta.raw_config.\"APT::Periodic::Unattended-Upgrade\"'` as non-root - Then I will see the following on stdout: - """ - "0" - """ - When I run `systemctl stop apt-daily.timer` with sudo - And I run `rm /etc/apt/apt.conf.d/99test` with sudo - And I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.systemd_apt_timer_enabled` as non-root - Then I will see the following on stdout: - """ - false - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_running` as non-root - Then I will see the following on stdout: - """ - false - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.msg` as non-root - Then I will see the following on stdout: - """ - "apt-daily.timer jobs are not running" - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.code` as non-root - Then I will see the following on stdout: - """ - "unattended-upgrades-systemd-job-disabled" - """ - When I create the file `/etc/apt/apt.conf.d/50unattended-upgrades` with the following: - """ - APT::Periodic::Unattended-Upgrade "1"; - """ - And I run `systemctl start apt-daily.timer` with sudo - And I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_frequency_days` as non-root - Then I will see the following on stdout: - """ - 1 - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.systemd_apt_timer_enabled` as non-root - Then I will see the following on stdout: - """ - true - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_allowed_origins` as non-root - Then I will see the following on stdout: - """ - [] - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_running` as non-root - Then I will see the following on stdout: - """ - false - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.msg` as non-root - Then I will see the following on stdout: - """ - "Unattended-Upgrade::Allowed-Origins is empty" - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.code` as non-root - Then I will see the following on stdout: - """ - "unattended-upgrades-cfg-list-value-empty" - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq '.data.meta.raw_config.\"Unattended-Upgrade::Allowed-Origins\"'` as non-root - Then I will see the following on stdout: - """ - null - """ - When I run `/usr/lib/apt/apt.systemd.daily update` with sudo - And I run `/usr/lib/apt/apt.systemd.daily install` with sudo - And I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_last_run` as non-root - Then stdout matches regexp: - """ - "(?!null).*" - """ - When I create the file `/etc/apt/apt.conf.d/99test` with the following: - """ - Unattended-Upgrade::Mail "mail"; - Unattended-Upgrade::Package-Blacklist { - "vim"; - }; - """ - And I run shell command `pro api u.unattended_upgrades.status.v1 | jq '.data.meta.raw_config.\"Unattended-Upgrade::Mail\"'` as non-root - Then I will see the following on stdout: - """ - "mail" - """ - When I run shell command `pro api u.unattended_upgrades.status.v1 | jq '.data.meta.raw_config.\"Unattended-Upgrade::Package-Blacklist\"'` as non-root - Then I will see the following on stdout: - """ - [ - "vim" - ] - """ - When I apt remove `unattended-upgrades` - And I run `pro api u.unattended_upgrades.status.v1` as non-root - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"apt_periodic_job_enabled": false, "package_lists_refresh_frequency_days": 0, "systemd_apt_timer_enabled": false, "unattended_upgrades_allowed_origins": \[\], "unattended_upgrades_disabled_reason": {"code": "unattended-upgrades-uninstalled", "msg": "unattended-upgrades package is not installed"}, "unattended_upgrades_frequency_days": 0, "unattended_upgrades_last_run": null, "unattended_upgrades_running": false}, "meta": {"environment_vars": \[\]}, "type": "UnattendedUpgradesStatus"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ + Scenario Outline: v1 unattended upgrades status + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro api u.unattended_upgrades.status.v1` as non-root + Then API data field output matches regexp: + """ + { + "attributes": { + "apt_periodic_job_enabled": true, + "package_lists_refresh_frequency_days": 1, + "systemd_apt_timer_enabled": true, + "unattended_upgrades_allowed_origins": [ + "\$\{distro_id\}:\$\{distro_codename\}", + "\$\{distro_id\}:\$\{distro_codename\}-security", + "\$\{distro_id\}ESMApps:\$\{distro_codename\}-apps-security", + "\$\{distro_id\}ESM:\$\{distro_codename\}-infra-security" + ], + "unattended_upgrades_disabled_reason": null, + "unattended_upgrades_frequency_days": 1, + "unattended_upgrades_last_run": null, + "unattended_upgrades_running": true + }, + "meta": { + "environment_vars": [], + "raw_config": { + "APT::Periodic::Enable": "1", + "APT::Periodic::Unattended-Upgrade": "1", + "APT::Periodic::Update-Package-Lists": "1", + "Unattended-Upgrade::Allowed-Origins": [ + "\$\{distro_id\}:\$\{distro_codename\}", + "\$\{distro_id\}:\$\{distro_codename\}-security", + "\$\{distro_id\}ESMApps:\$\{distro_codename\}-apps-security", + "\$\{distro_id\}ESM:\$\{distro_codename\}-infra-security" + ] + """ + When I create the file `/etc/apt/apt.conf.d/99test` with the following: + """ + APT::Periodic::Enable "0"; + """ + And I apt update + And I apt install `jq` + And I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.apt_periodic_job_enabled` as non-root + Then I will see the following on stdout: + """ + false + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_running` as non-root + Then I will see the following on stdout: + """ + false + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.msg` as non-root + Then I will see the following on stdout: + """ + "APT::Periodic::Enable is turned off" + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.code` as non-root + Then I will see the following on stdout: + """ + "unattended-upgrades-cfg-value-turned-off" + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq '.data.meta.raw_config.\"APT::Periodic::Enable\"'` as non-root + Then I will see the following on stdout: + """ + "0" + """ + When I create the file `/etc/apt/apt.conf.d/99test` with the following: + """ + APT::Periodic::Update-Package-Lists "0"; + """ + And I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.apt_periodic_job_enabled` as non-root + Then I will see the following on stdout: + """ + true + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.package_lists_refresh_frequency_days` as non-root + Then I will see the following on stdout: + """ + 0 + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_running` as non-root + Then I will see the following on stdout: + """ + false + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.msg` as non-root + Then I will see the following on stdout: + """ + "APT::Periodic::Update-Package-Lists is turned off" + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.code` as non-root + Then I will see the following on stdout: + """ + "unattended-upgrades-cfg-value-turned-off" + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq '.data.meta.raw_config.\"APT::Periodic::Update-Package-Lists\"'` as non-root + Then I will see the following on stdout: + """ + "0" + """ + When I create the file `/etc/apt/apt.conf.d/99test` with the following: + """ + APT::Periodic::Unattended-Upgrade "0"; + """ + And I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_frequency_days` as non-root + Then I will see the following on stdout: + """ + 0 + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.package_lists_refresh_frequency_days` as non-root + Then I will see the following on stdout: + """ + 1 + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_running` as non-root + Then I will see the following on stdout: + """ + false + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.msg` as non-root + Then I will see the following on stdout: + """ + "APT::Periodic::Unattended-Upgrade is turned off" + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.code` as non-root + Then I will see the following on stdout: + """ + "unattended-upgrades-cfg-value-turned-off" + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq '.data.meta.raw_config.\"APT::Periodic::Unattended-Upgrade\"'` as non-root + Then I will see the following on stdout: + """ + "0" + """ + When I run `systemctl stop apt-daily.timer` with sudo + And I run `rm /etc/apt/apt.conf.d/99test` with sudo + And I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.systemd_apt_timer_enabled` as non-root + Then I will see the following on stdout: + """ + false + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_running` as non-root + Then I will see the following on stdout: + """ + false + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.msg` as non-root + Then I will see the following on stdout: + """ + "apt-daily.timer jobs are not running" + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.code` as non-root + Then I will see the following on stdout: + """ + "unattended-upgrades-systemd-job-disabled" + """ + When I create the file `/etc/apt/apt.conf.d/50unattended-upgrades` with the following: + """ + APT::Periodic::Unattended-Upgrade "1"; + """ + And I run `systemctl start apt-daily.timer` with sudo + And I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_frequency_days` as non-root + Then I will see the following on stdout: + """ + 1 + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.systemd_apt_timer_enabled` as non-root + Then I will see the following on stdout: + """ + true + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_allowed_origins` as non-root + Then I will see the following on stdout: + """ + [] + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_running` as non-root + Then I will see the following on stdout: + """ + false + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.msg` as non-root + Then I will see the following on stdout: + """ + "Unattended-Upgrade::Allowed-Origins is empty" + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_disabled_reason.code` as non-root + Then I will see the following on stdout: + """ + "unattended-upgrades-cfg-list-value-empty" + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq '.data.meta.raw_config.\"Unattended-Upgrade::Allowed-Origins\"'` as non-root + Then I will see the following on stdout: + """ + null + """ + When I run `/usr/lib/apt/apt.systemd.daily update` with sudo + And I run `/usr/lib/apt/apt.systemd.daily install` with sudo + And I run shell command `pro api u.unattended_upgrades.status.v1 | jq .data.attributes.unattended_upgrades_last_run` as non-root + Then stdout matches regexp: + """ + "(?!null).*" + """ + When I create the file `/etc/apt/apt.conf.d/99test` with the following: + """ + Unattended-Upgrade::Mail "mail"; + Unattended-Upgrade::Package-Blacklist { + "vim"; + }; + """ + And I run shell command `pro api u.unattended_upgrades.status.v1 | jq '.data.meta.raw_config.\"Unattended-Upgrade::Mail\"'` as non-root + Then I will see the following on stdout: + """ + "mail" + """ + When I run shell command `pro api u.unattended_upgrades.status.v1 | jq '.data.meta.raw_config.\"Unattended-Upgrade::Package-Blacklist\"'` as non-root + Then I will see the following on stdout: + """ + [ + "vim" + ] + """ + When I apt remove `unattended-upgrades` + And I run `pro api u.unattended_upgrades.status.v1` as non-root + Then API data field output matches regexp: + """ + { + "attributes": { + "apt_periodic_job_enabled": false, + "package_lists_refresh_frequency_days": 0, + "systemd_apt_timer_enabled": false, + "unattended_upgrades_allowed_origins": [], + "unattended_upgrades_disabled_reason": { + "code": "unattended-upgrades-uninstalled", + "msg": "unattended-upgrades package is not installed" + }, + "unattended_upgrades_frequency_days": 0, + "unattended_upgrades_last_run": null, + "unattended_upgrades_running": false + }, + "meta": { + "environment_vars": [] + }, + "type": "UnattendedUpgradesStatus" + } + """ - Examples: ubuntu release - | release | machine_type | extra_field | - | xenial | lxd-container | | - | bionic | lxd-container | "Unattended-Upgrade::DevRelease": "false" | - | focal | lxd-container | "Unattended-Upgrade::DevRelease": "auto" | - | jammy | lxd-container | "Unattended-Upgrade::DevRelease": "auto" | - | mantic | lxd-container | "Unattended-Upgrade::DevRelease": "auto" | + Examples: ubuntu release + | release | machine_type | extra_field | + | xenial | lxd-container | | + | bionic | lxd-container | ,\n"Unattended-Upgrade::DevRelease": "false" | + | focal | lxd-container | ,\n"Unattended-Upgrade::DevRelease": "auto" | + | jammy | lxd-container | ,\n"Unattended-Upgrade::DevRelease": "auto" | + | mantic | lxd-container | ,\n"Unattended-Upgrade::DevRelease": "auto" | + | noble | lxd-container | ,\n"Unattended-Upgrade::DevRelease": "auto" | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/apt_messages.feature ubuntu-advantage-tools-32~16.04/features/apt_messages.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/apt_messages.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/apt_messages.feature 2024-05-10 17:07:05.000000000 +0000 @@ -1,746 +1,1114 @@ Feature: APT Messages - @uses.config.contract_token - Scenario Outline: APT JSON Hook prints package counts correctly on xenial - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - When I apt update - When I apt upgrade - When I apt install `` - When I apt upgrade - Then stdout matches regexp: - """ - 1 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - 1 standard LTS security update - - """ - - When I apt install `` - When I apt upgrade - Then stdout matches regexp: - """ - 2 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - 2 esm-infra security updates - - """ - - When I apt install `` - When I apt upgrade - Then stdout matches regexp: - """ - 1 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - 1 esm-apps security update - - """ - - When I apt install ` ` - When I apt upgrade - Then stdout matches regexp: - """ - 3 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - 1 standard LTS security update and 2 esm-infra security updates - - """ - - When I apt install ` ` - When I apt upgrade - Then stdout matches regexp: - """ - 2 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - 1 standard LTS security update and 1 esm-apps security update - - """ - - When I apt install ` ` - When I apt upgrade - Then stdout matches regexp: - """ - 3 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - 2 esm-infra security updates and 1 esm-apps security update - - """ - - When I apt install ` ` - When I apt upgrade - Then stdout matches regexp: - """ - 4 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - 1 standard LTS security update, 2 esm-infra security updates and 1 esm-apps security update - - """ - - When I apt upgrade - Then stdout matches regexp: - """ - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - Then stdout does not match regexp: - """ - standard LTS security update - """ - Then stdout does not match regexp: - """ - esm-infra - """ - Then stdout does not match regexp: - """ - esm-apps - """ - - Examples: ubuntu release - | release | machine_type | standard-pkg | infra-pkg | apps-pkg | - | xenial | lxd-container | wget=1.17.1-1ubuntu1 | curl=7.47.0-1ubuntu2 libcurl3-gnutls=7.47.0-1ubuntu2 | hello=2.10-1 | - - @uses.config.contract_token - Scenario Outline: APT Hook advertises esm-infra on upgrade - Given a `` `` machine with ubuntu-advantage-tools installed - When I apt update - When I apt upgrade - When I apt autoremove - When I run `pro config set apt_news=false` with sudo - When I run `pro refresh messages` with sudo - When I apt upgrade - Then stdout matches regexp: - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - The following security updates require Ubuntu Pro with 'esm-infra' enabled: - ([-+.\w\s]*) - Learn more about Ubuntu Pro for \.04 at https:\/\/ubuntu\.com\/-04 - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded\. - """ - When I apt-get upgrade - Then I will see the following on stdout: - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I attach `contract_token` with sudo - When I apt upgrade on a dry run - Then stdout matches regexp: - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - The following packages will be upgraded: - """ - When I apt upgrade - When I run `pro detach --assume-yes` with sudo - When I run `pro refresh messages` with sudo - When I apt upgrade - Then stdout matches regexp: - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded\. - """ - Examples: ubuntu release - | release | machine_type | version | - | xenial | lxd-container | 16 | - | bionic | lxd-container | 18 | - - @uses.config.contract_token - Scenario Outline: APT Hook advertises esm-apps on upgrade - Given a `` `` machine with ubuntu-advantage-tools installed - When I apt update - When I apt upgrade including phased updates - When I apt autoremove - When I apt install `` - When I run `pro config set apt_news=false` with sudo - When I run `pro refresh messages` with sudo - When I apt upgrade - Then stdout matches regexp: - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - Get through Ubuntu Pro with 'esm-apps' enabled: - - - 0 upgraded, 0 newly installed, 0 to remove and \d+ not upgraded. - """ - When I apt-get upgrade - Then stdout matches regexp: - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - 0 upgraded, 0 newly installed, 0 to remove and \d+ not upgraded. - """ - When I attach `contract_token` with sudo - When I apt upgrade on a dry run - Then stdout matches regexp: - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - The following packages will be upgraded: - - """ - When I apt upgrade - When I run `pro detach --assume-yes` with sudo - When I run `pro refresh messages` with sudo - When I apt upgrade - Then stdout matches regexp: - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - 0 upgraded, 0 newly installed, 0 to remove and \d+ not upgraded\. - """ - Examples: ubuntu release - | release | machine_type | package | more_msg | learn_more_msg | - | focal | lxd-container | hello | another security update | Learn more about Ubuntu Pro at https://ubuntu.com/pro | - | jammy | lxd-container | hello | another security update | Learn more about Ubuntu Pro at https://ubuntu.com/pro | - - @uses.config.contract_token - Scenario Outline: APT News - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - # On interim releases we will not enable any service, so we need a manual apt update - When I apt update - When I apt upgrade including phased updates - When I apt autoremove - When I apt install `jq` - When I run `pro detach --assume-yes` with sudo - - Given a `focal` `` machine named `apt-news-server` - When I apt update on the `apt-news-server` machine - When I apt install `nginx` on the `apt-news-server` machine - When I run `sed -i "s/gzip on;/gzip on;\n\tgzip_min_length 1;\n\tgzip_types application\/json;\n/" /etc/nginx/nginx.conf` `with sudo` on the `apt-news-server` machine - When I run `systemctl restart nginx` `with sudo` on the `apt-news-server` machine - - When I run `pro config set apt_news_url=http://$behave_var{machine-ip apt-news-server}/aptnews.json` with sudo - - When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: - """ - { - "messages": [ - { - "begin": "$behave_var{today}", - "lines": [ - "one" + @uses.config.contract_token + Scenario Outline: APT JSON Hook prints package counts correctly on xenial + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + When I apt upgrade + When I apt install `` + When I apt upgrade + Then stdout matches regexp: + """ + 1 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + 1 standard LTS security update + """ + When I apt install `` + When I apt upgrade + Then stdout matches regexp: + """ + 2 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + 2 esm-infra security updates + """ + When I apt install `` + When I apt upgrade + Then stdout matches regexp: + """ + 1 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + 1 esm-apps security update + """ + When I apt install ` ` + When I apt upgrade + Then stdout matches regexp: + """ + 3 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + 1 standard LTS security update and 2 esm-infra security updates + """ + When I apt install ` ` + When I apt upgrade + Then stdout matches regexp: + """ + 2 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + 1 standard LTS security update and 1 esm-apps security update + """ + When I apt install ` ` + When I apt upgrade + Then stdout matches regexp: + """ + 3 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + 2 esm-infra security updates and 1 esm-apps security update + """ + When I apt install ` ` + When I apt upgrade + Then stdout matches regexp: + """ + 4 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + 1 standard LTS security update, 2 esm-infra security updates and 1 esm-apps security update + """ + When I apt upgrade + Then stdout matches regexp: + """ + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + Then stdout does not match regexp: + """ + standard LTS security update + """ + Then stdout does not match regexp: + """ + esm-infra + """ + Then stdout does not match regexp: + """ + esm-apps + """ + + Examples: ubuntu release + | release | machine_type | standard-pkg | infra-pkg | apps-pkg | + | xenial | lxd-container | wget=1.17.1-1ubuntu1 | curl=7.47.0-1ubuntu2 libcurl3-gnutls=7.47.0-1ubuntu2 | hello=2.10-1 | + + @uses.config.contract_token + Scenario Outline: APT Hook advertises esm-infra on upgrade + Given a `` `` machine with ubuntu-advantage-tools installed + When I remove support for `backports` in APT + When I apt update + When I apt upgrade + When I apt autoremove + When I run `pro config set apt_news=false` with sudo + When I run `pro refresh messages` with sudo + When I apt upgrade + Then stdout matches regexp: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + The following security updates require Ubuntu Pro with 'esm-infra' enabled: + ([-+.\w\s]*) + Learn more about Ubuntu Pro for \.04 at https:\/\/ubuntu\.com\/-04 + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded\. + """ + When I apt-get upgrade + Then I will see the following on stdout: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I attach `contract_token` with sudo + When I apt upgrade on a dry run + Then stdout matches regexp: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + The following packages will be upgraded: + """ + When I apt upgrade + When I run `pro detach --assume-yes` with sudo + When I run `pro refresh messages` with sudo + When I apt upgrade + Then stdout matches regexp: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded\. + """ + + Examples: ubuntu release + | release | machine_type | version | + | xenial | lxd-container | 16 | + | bionic | lxd-container | 18 | + | bionic | wsl | 18 | + + @uses.config.contract_token + Scenario Outline: APT Hook advertises esm-apps on upgrade + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt upgrade including phased updates + When I apt autoremove + When I apt install `` + When I run `pro config set apt_news=false` with sudo + When I run `pro refresh messages` with sudo + When I apt upgrade + Then stdout matches regexp: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + Get through Ubuntu Pro with 'esm-apps' enabled: + + + 0 upgraded, 0 newly installed, 0 to remove and \d+ not upgraded. + """ + When I apt-get upgrade + Then stdout matches regexp: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and \d+ not upgraded. + """ + When I attach `contract_token` with sudo + When I apt upgrade on a dry run + Then stdout matches regexp: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + The following packages will be upgraded: + + """ + When I apt upgrade + When I run `pro detach --assume-yes` with sudo + When I run `pro refresh messages` with sudo + When I apt upgrade + Then stdout matches regexp: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and \d+ not upgraded\. + """ + + Examples: ubuntu release + | release | machine_type | package | more_msg | learn_more_msg | + # TODO add noble when there is a package available in esm with a higher version than in noble (not true of hello) + # | noble | lxd-container | hello | another security update | Learn more about Ubuntu Pro at https://ubuntu.com/pro | + | focal | lxd-container | hello | another security update | Learn more about Ubuntu Pro at https://ubuntu.com/pro | + | focal | wsl | hello | another security update | Learn more about Ubuntu Pro at https://ubuntu.com/pro | + | jammy | lxd-container | hello | another security update | Learn more about Ubuntu Pro at https://ubuntu.com/pro | + | jammy | wsl | hello | another security update | Learn more about Ubuntu Pro at https://ubuntu.com/pro | + + @uses.config.contract_token + Scenario Outline: APT News + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + When I remove support for `backports` in APT + When I apt upgrade including phased updates + When I apt autoremove + When I apt install `jq` + When I run `pro detach --assume-yes` with sudo + # We are doing this because ESM pin might prevent some packages to be upgraded (i.e. + # distro-info-data) + When I apt upgrade + Given a `focal` `` machine named `apt-news-server` + When I apt install `nginx` on the `apt-news-server` machine + When I run `sed -i "s/gzip on;/gzip on;\n\tgzip_min_length 1;\n\tgzip_types application\/json;\n/" /etc/nginx/nginx.conf` `with sudo` on the `apt-news-server` machine + When I run `systemctl restart nginx` `with sudo` on the `apt-news-server` machine + When I run `pro config set apt_news_url=http://$behave_var{machine-ip apt-news-server}/aptnews.json` with sudo + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "lines": [ + "one" + ] + } + ] + } + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + # + # one + # + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root + Then I will see the following on stdout + """ + "one" + """ + # Test that it is not shown in apt-get output + When I apt-get upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "lines": [ + "one", + "two", + "three" + ] + } + ] + } + """ + # apt update stamp will prevent a apt_news refresh + When I apt update + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + # + # one + # + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root + Then I will see the following on stdout + """ + "one" + """ + # manual refresh gets new message + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + # + # one + # two + # three + # + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root + Then I will see the following on stdout + """ + "one\ntwo\nthree" + """ + # creates /run/ubuntu-advantage and /var/lib/ubuntu-advantage/messages if not there + When I run `rm -rf /run/ubuntu-advantage` with sudo + When I run `rm -rf /var/lib/ubuntu-advantage/messages` with sudo + When I run `rm /var/lib/apt/periodic/update-success-stamp` with sudo + When I apt update + # the apt-news.service unit runs in the background, give it some time to fetch the json file + When I wait `5` seconds + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + # + # one + # two + # three + # + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root + Then I will see the following on stdout + """ + "one\ntwo\nthree" + """ + # more than 3 lines ignored + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "lines": [ + "one", + "two", + "three", + "four" + ] + } + ] + } + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root + Then I will see the following on stdout + """ + null + """ + # more than 77 chars ignored + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "lines": [ + "000000000100000000020000000003000000000400000000050000000006000000000712345678" + ] + } + ] + } + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root + Then I will see the following on stdout + """ + null + """ + # end is respected + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today -3}", + "end": "$behave_var{today -1}", + "lines": [ + "one" + ] + } + ] + } + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root + Then I will see the following on stdout + """ + null + """ + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today -3}", + "end": "$behave_var{today +1}", + "lines": [ + "one" + ] + } + ] + } + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + # + # one + # + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root + Then I will see the following on stdout + """ + "one" + """ + # begin >30 days ago ignored, even if end is set to future + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today -31}", + "end": "$behave_var{today +1}", + "lines": [ + "one" + ] + } + ] + } + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root + Then I will see the following on stdout + """ + null + """ + # begin in future + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today +1}", + "lines": [ + "one" + ] + } + ] + } + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root + Then I will see the following on stdout + """ + null + """ + # local apt news overrides for contract expiry notices + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "lines": [ + "one" + ] + } + ] + } + """ + When I attach `contract_token` with sudo + When I apt upgrade + When I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + effectiveTo: $behave_var{today +2} + """ + # test that apt update will trigger hook to update apt_news for local override + When I run `rm -f /var/lib/apt/periodic/update-success-stamp` with sudo + When I apt update + # the apt-news.service unit runs in the background, give it some time to fetch the json file + When I wait `5` seconds + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + # + # CAUTION: Your Ubuntu Pro subscription will expire in 2 days. + # Renew your subscription at https://ubuntu.com/pro/dashboard to ensure + # continued security coverage for your applications. + # + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root + Then I will see the following on stdout + """ + "CAUTION: Your Ubuntu Pro subscription will expire in 2 days.\nRenew your subscription at https://ubuntu.com/pro/dashboard to ensure\ncontinued security coverage for your applications." + """ + When I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + effectiveTo: $behave_var{today -3} + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then stdout matches regexp: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + # + # CAUTION: Your Ubuntu Pro subscription expired on \d+ \w+ \d+. + # Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard to ensure + # continued security coverage for your applications. + # Your grace period will expire in 11 days. + # + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root + Then stdout matches regexp: + """ + "CAUTION: Your Ubuntu Pro subscription expired on \d+ \w+ \d+.\\nRenew your subscription at https:\/\/ubuntu.com\/pro\/dashboard to ensure\\ncontinued security coverage for your applications.\\nYour grace period will expire in 11 days." + """ + When I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + effectiveTo: $behave_var{today -20} + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + # + # *Your Ubuntu Pro subscription has EXPIRED* + # Renew your subscription at https://ubuntu.com/pro/dashboard + # + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root + Then I will see the following on stdout + """ + "*Your Ubuntu Pro subscription has EXPIRED*\nRenew your subscription at https://ubuntu.com/pro/dashboard" + """ + When I create the file `/var/lib/ubuntu-advantage/machine-token-overlay.json` with the following: + """ + { + "machineTokenInfo": { + "contractInfo": { + "effectiveTo": null + } + } + } + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + # + # *Your Ubuntu Pro subscription has EXPIRED* + # Renew your subscription at https://ubuntu.com/pro/dashboard + # + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root + Then I will see the following on stdout + """ + "*Your Ubuntu Pro subscription has EXPIRED*\nRenew your subscription at https://ubuntu.com/pro/dashboard" + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | xenial | lxd-vm | + | bionic | lxd-container | + | bionic | lxd-vm | + | focal | lxd-container | + | focal | lxd-vm | + | jammy | lxd-container | + | jammy | lxd-vm | + | mantic | lxd-container | + | mantic | lxd-vm | + | noble | lxd-container | + | noble | lxd-vm | + + Scenario Outline: Cloud and series-specific URLs + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt install `ansible` + # Update after installing to make sure messages are there + When I apt update + When I apt upgrade on a dry run + Then stdout contains substring: + """ + + """ + + Examples: release-per-machine-type + | release | machine_type | msg | + | xenial | aws.generic | Learn more about Ubuntu Pro for 16.04 at https://ubuntu.com/16-04 | + | xenial | azure.generic | Learn more about Ubuntu Pro for 16.04 on Azure at https://ubuntu.com/16-04/azure | + | xenial | gcp.generic | Learn more about Ubuntu Pro for 16.04 at https://ubuntu.com/16-04 | + | bionic | aws.generic | Learn more about Ubuntu Pro for 18.04 at https://ubuntu.com/18-04 | + | bionic | azure.generic | Learn more about Ubuntu Pro for 18.04 on Azure at https://ubuntu.com/18-04/azure | + | bionic | gcp.generic | Learn more about Ubuntu Pro for 18.04 at https://ubuntu.com/18-04 | + | focal | aws.generic | Learn more about Ubuntu Pro on AWS at https://ubuntu.com/aws/pro | + | focal | azure.generic | Learn more about Ubuntu Pro on Azure at https://ubuntu.com/azure/pro | + | focal | gcp.generic | Learn more about Ubuntu Pro on GCP at https://ubuntu.com/gcp/pro | + + @uses.config.contract_token + Scenario Outline: APT news selectors + Given a `` `` machine with ubuntu-advantage-tools installed + When I remove support for `backports` in APT + When I attach `contract_token` with sudo + When I apt upgrade including phased updates + When I apt autoremove + When I apt install `jq` + When I run `pro detach --assume-yes` with sudo + # We are doing this because ESM pin might prevent some packages to be upgraded (i.e. + # distro-info-data) + When I apt upgrade + Given a `mantic` `` machine named `apt-news-server` + When I apt install `nginx` on the `apt-news-server` machine + When I run `sed -i "s/gzip on;/gzip on;\n\tgzip_min_length 1;\n\tgzip_types application\/json;\n/" /etc/nginx/nginx.conf` `with sudo` on the `apt-news-server` machine + When I run `systemctl restart nginx` `with sudo` on the `apt-news-server` machine + When I run `pro config set apt_news_url=http://$behave_var{machine-ip apt-news-server}/aptnews.json` with sudo + # Testing codename selector + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "selectors": { + "codenames": [""], + }, + "lines": [ + "one" + ] + } + ] + } + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "selectors": { + "codenames": [""] + }, + "lines": [ + "one", + "two" + ] + } + ] + } + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + # + # one + # two + # + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + # Testing architectures selector + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "selectors": { + "architectures": ["amd64"] + }, + "lines": [ + "one", + "two" + ] + } + ] + } + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + # + # one + # two + # + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "selectors": { + "architectures": ["arm64"] + }, + "lines": [ + "one", + "two" + ] + } + ] + } + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "selectors": { + "architectures": ["arm64", "amd64"] + }, + "lines": [ + "one", + "two" + ] + } + ] + } + """ + When I run `pro refresh messages` with sudo + When I apt upgrade + Then I will see the following on stdout: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + # + # one + # two + # + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + # Testing packages selector when package is not installed + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "selectors": { + "codenames": [""], + "architectures": ["amd64"], + "packages": [["libcurl4", "==", ""]] + }, + "lines": [ + "one", + "two" + ] + } + ] + } + """ + And I run `pro refresh messages` with sudo + And I apt upgrade + Then I will see the following on stdout: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + # Testing package selector when package installed + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "selectors": { + "packages": [["", "==", ""]] + }, + "lines": [ + "one", + "two" + ] + } + ] + } + """ + And I apt install `=` + And I run `apt-mark hold ` with sudo + And I run `pro refresh messages` with sudo + And I apt upgrade + Then stdout contains substring: + """ + # + # one + # two + # + """ + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "selectors": { + "packages": [["", "<", "8.2.1-1ubuntu3.2"]] + }, + "lines": [ + "one", + "two" + ] + } + ] + } + """ + And I run `pro refresh messages` with sudo + And I apt upgrade + Then stdout contains substring: + """ + # + # one + # two + # + """ + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "selectors": { + "codenames": [""], + "architectures": ["amd64"], + "packages": [["", "==", ""]] + }, + "lines": [ + "one", + "two" + ] + } + ] + } + """ + And I run `pro refresh messages` with sudo + And I apt upgrade + Then stdout contains substring: + """ + # + # one + # two + # + """ + # Still displayed if one package selector fails + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "selectors": { + "packages": [ + ["", "==", ""], + ["linux", "<", "1"] ] - } - ] - } - """ - When I run `pro refresh messages` with sudo - When I apt upgrade - Then I will see the following on stdout - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - # - # one - # - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root - Then I will see the following on stdout - """ - "one" - """ - - # Test that it is not shown in apt-get output - When I apt-get upgrade - Then I will see the following on stdout - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - - When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: - """ - { - "messages": [ - { - "begin": "$behave_var{today}", - "lines": [ - "one", - "two", - "three" - ] - } - ] - } - """ - # apt update stamp will prevent a apt_news refresh - When I apt update - When I apt upgrade - Then I will see the following on stdout - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - # - # one - # - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root - Then I will see the following on stdout - """ - "one" - """ - - # manual refresh gets new message - When I run `pro refresh messages` with sudo - When I apt upgrade - Then I will see the following on stdout - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - # - # one - # two - # three - # - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root - Then I will see the following on stdout - """ - "one\ntwo\nthree" - """ - - # creates /run/ubuntu-advantage and /var/lib/ubuntu-advantage/messages if not there - When I run `rm -rf /run/ubuntu-advantage` with sudo - When I run `rm -rf /var/lib/ubuntu-advantage/messages` with sudo - When I run `rm /var/lib/apt/periodic/update-success-stamp` with sudo - When I apt update - # the apt-news.service unit runs in the background, give it some time to fetch the json file - When I wait `5` seconds - When I apt upgrade - Then I will see the following on stdout - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - # - # one - # two - # three - # - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root - Then I will see the following on stdout - """ - "one\ntwo\nthree" - """ - - # more than 3 lines ignored - When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: - """ - { - "messages": [ - { - "begin": "$behave_var{today}", - "lines": [ - "one", - "two", - "three", - "four" - ] - } - ] - } - """ - When I run `pro refresh messages` with sudo - When I apt upgrade - Then I will see the following on stdout - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root - Then I will see the following on stdout - """ - null - """ - - # more than 77 chars ignored - When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: - """ - { - "messages": [ - { - "begin": "$behave_var{today}", - "lines": [ - "000000000100000000020000000003000000000400000000050000000006000000000712345678" - ] - } - ] - } - """ - When I run `pro refresh messages` with sudo - When I apt upgrade - Then I will see the following on stdout - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root - Then I will see the following on stdout - """ - null - """ - - # end is respected - When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: - """ - { - "messages": [ - { - "begin": "$behave_var{today -3}", - "end": "$behave_var{today -1}", - "lines": [ - "one" - ] - } - ] - } - """ - When I run `pro refresh messages` with sudo - When I apt upgrade - Then I will see the following on stdout - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root - Then I will see the following on stdout - """ - null - """ - When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: - """ - { - "messages": [ - { - "begin": "$behave_var{today -3}", - "end": "$behave_var{today +1}", - "lines": [ - "one" - ] - } - ] - } - """ - When I run `pro refresh messages` with sudo - When I apt upgrade - Then I will see the following on stdout - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - # - # one - # - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root - Then I will see the following on stdout - """ - "one" - """ - - # begin >30 days ago ignored, even if end is set to future - When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: - """ - { - "messages": [ - { - "begin": "$behave_var{today -31}", - "end": "$behave_var{today +1}", - "lines": [ - "one" - ] - } - ] - } - """ - When I run `pro refresh messages` with sudo - When I apt upgrade - Then I will see the following on stdout - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root - Then I will see the following on stdout - """ - null - """ - - # begin in future - When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: - """ - { - "messages": [ - { - "begin": "$behave_var{today +1}", - "lines": [ - "one" - ] - } - ] - } - """ - When I run `pro refresh messages` with sudo - When I apt upgrade - Then I will see the following on stdout - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root - Then I will see the following on stdout - """ - null - """ - - # local apt news overrides for contract expiry notices - When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: - """ - { - "messages": [ - { - "begin": "$behave_var{today}", - "lines": [ - "one" - ] - } - ] - } - """ - When I attach `contract_token` with sudo - When I apt upgrade - When I set the machine token overlay to the following yaml - """ - machineTokenInfo: - contractInfo: - effectiveTo: $behave_var{today +2} - """ - # test that apt update will trigger hook to update apt_news for local override - When I run `rm -f /var/lib/apt/periodic/update-success-stamp` with sudo - When I apt update - # the apt-news.service unit runs in the background, give it some time to fetch the json file - When I wait `5` seconds - When I apt upgrade - Then I will see the following on stdout - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - # - # CAUTION: Your Ubuntu Pro subscription will expire in 2 days. - # Renew your subscription at https://ubuntu.com/pro/dashboard to ensure - # continued security coverage for your applications. - # - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root - Then I will see the following on stdout - """ - "CAUTION: Your Ubuntu Pro subscription will expire in 2 days.\nRenew your subscription at https://ubuntu.com/pro/dashboard to ensure\ncontinued security coverage for your applications." - """ - When I set the machine token overlay to the following yaml - """ - machineTokenInfo: - contractInfo: - effectiveTo: $behave_var{today -3} - """ - When I run `pro refresh messages` with sudo - When I apt upgrade - Then stdout matches regexp: - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - # - # CAUTION: Your Ubuntu Pro subscription expired on \d+ \w+ \d+. - # Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard to ensure - # continued security coverage for your applications. - # Your grace period will expire in 11 days. - # - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root - Then stdout matches regexp: - """ - "CAUTION: Your Ubuntu Pro subscription expired on \d+ \w+ \d+.\\nRenew your subscription at https:\/\/ubuntu.com\/pro\/dashboard to ensure\\ncontinued security coverage for your applications.\\nYour grace period will expire in 11 days." - """ - When I set the machine token overlay to the following yaml - """ - machineTokenInfo: - contractInfo: - effectiveTo: $behave_var{today -20} - """ - When I run `pro refresh messages` with sudo - When I apt upgrade - Then I will see the following on stdout - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - # - # *Your Ubuntu Pro subscription has EXPIRED* - # Renew your subscription at https://ubuntu.com/pro/dashboard - # - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root - Then I will see the following on stdout - """ - "*Your Ubuntu Pro subscription has EXPIRED*\nRenew your subscription at https://ubuntu.com/pro/dashboard" - """ - When I create the file `/var/lib/ubuntu-advantage/machine-token-overlay.json` with the following: - """ - { - "machineTokenInfo": { - "contractInfo": { - "effectiveTo": null - } - } - } - """ - When I run `pro refresh messages` with sudo - When I apt upgrade - Then I will see the following on stdout - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - # - # *Your Ubuntu Pro subscription has EXPIRED* - # Renew your subscription at https://ubuntu.com/pro/dashboard - # - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I run shell command `pro api u.apt_news.current_news.v1 | jq .data.attributes.current_news` as non-root - Then I will see the following on stdout - """ - "*Your Ubuntu Pro subscription has EXPIRED*\nRenew your subscription at https://ubuntu.com/pro/dashboard" - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - Scenario Outline: Cloud and series-specific URLs - Given a `` `` machine with ubuntu-advantage-tools installed - When I apt update - When I apt install `ansible` - # Update after installing to make sure messages are there - When I apt update - When I apt upgrade on a dry run - Then stdout contains substring: - """ - - """ - Examples: release-per-machine-type - | release | machine_type | msg | - | xenial | aws.generic | Learn more about Ubuntu Pro for 16.04 at https://ubuntu.com/16-04 | - | xenial | azure.generic | Learn more about Ubuntu Pro for 16.04 on Azure at https://ubuntu.com/16-04/azure | - | xenial | gcp.generic | Learn more about Ubuntu Pro for 16.04 at https://ubuntu.com/16-04 | - | bionic | aws.generic | Learn more about Ubuntu Pro for 18.04 at https://ubuntu.com/18-04 | - | bionic | azure.generic | Learn more about Ubuntu Pro for 18.04 on Azure at https://ubuntu.com/18-04/azure | - | bionic | gcp.generic | Learn more about Ubuntu Pro for 18.04 at https://ubuntu.com/18-04 | - | focal | aws.generic | Learn more about Ubuntu Pro on AWS at https://ubuntu.com/aws/pro | - | focal | azure.generic | Learn more about Ubuntu Pro on Azure at https://ubuntu.com/azure/pro | - | focal | gcp.generic | Learn more about Ubuntu Pro on GCP at https://ubuntu.com/gcp/pro | - - @uses.config.contract_token - Scenario Outline: APT Hook do not advertises esm-apps on upgrade for interim releases - Given a `` `` machine with ubuntu-advantage-tools installed - When I apt update - When I apt upgrade including phased updates - When I apt autoremove - When I apt install `hello` - When I run `pro config set apt_news=false` with sudo - When I run `pro refresh messages` with sudo - When I apt upgrade - Then stdout does not match regexp: - """ - Get more security updates through Ubuntu Pro with 'esm-apps' enabled: - """ - When I apt-get upgrade - Then I will see the following on stdout: - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. - """ - When I attach `contract_token` with sudo - When I apt upgrade on a dry run - Then stdout matches regexp: - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded\. - """ - When I apt upgrade - When I run `pro detach --assume-yes` with sudo - When I run `pro refresh messages` with sudo - When I apt upgrade - Then stdout matches regexp: - """ - Reading package lists... - Building dependency tree... - Reading state information... - Calculating upgrade... - 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded\. - """ - Examples: ubuntu release - | release | machine_type | - | mantic | lxd-container | + }, + "lines": [ + "one", + "two" + ] + } + ] + } + """ + And I run `pro refresh messages` with sudo + And I apt upgrade + Then stdout contains substring: + """ + # + # one + # two + # + """ + # Testing multiple selectors together + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "selectors": { + "codenames": [""], + "architectures": ["arm64"], + "packages": [["", "==", ""]] + }, + "lines": [ + "one", + "two" + ] + } + ] + } + """ + And I run `pro refresh messages` with sudo + And I apt upgrade + Then I will see the following on stdout: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + The following packages have been kept back: + + 0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded. + """ + When I create the file `/var/www/html/aptnews.json` on the `apt-news-server` machine with the following: + """ + { + "messages": [ + { + "begin": "$behave_var{today}", + "selectors": { + "codenames": [""], + "architectures": ["amd64"], + "packages": [["", ">", ""]] + }, + "lines": [ + "one", + "two" + ] + } + ] + } + """ + And I run `pro refresh messages` with sudo + And I apt upgrade + Then I will see the following on stdout: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + The following packages have been kept back: + + 0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded. + """ + + Examples: ubuntu release + | release | machine_type | wrong_release | package | installed_version | + | xenial | lxd-container | bionic | libcurl3-gnutls | 7.47.0-1ubuntu2 | + | bionic | lxd-container | focal | libcurl4 | 7.58.0-2ubuntu3 | + | focal | lxd-container | bionic | libcurl4 | 7.68.0-1ubuntu2 | + | jammy | lxd-container | focal | libcurl4 | 7.81.0-1 | + | mantic | lxd-container | jammy | libcurl4 | 8.2.1-1ubuntu3 | + + @uses.config.contract_token + Scenario Outline: APT Hook do not advertises esm-apps on upgrade for interim releases + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt upgrade including phased updates + When I apt autoremove + When I apt install `hello` + When I run `pro config set apt_news=false` with sudo + When I run `pro refresh messages` with sudo + When I apt upgrade + Then stdout does not match regexp: + """ + Get more security updates through Ubuntu Pro with 'esm-apps' enabled: + """ + When I apt-get upgrade + Then I will see the following on stdout: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. + """ + When I attach `contract_token` with sudo + When I apt upgrade on a dry run + Then stdout matches regexp: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded\. + """ + When I apt upgrade + When I run `pro detach --assume-yes` with sudo + When I run `pro refresh messages` with sudo + When I apt upgrade + Then stdout matches regexp: + """ + Reading package lists... + Building dependency tree... + Reading state information... + Calculating upgrade... + 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded\. + """ + + Examples: ubuntu release + | release | machine_type | + | mantic | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/attach_invalidtoken.feature ubuntu-advantage-tools-32~16.04/features/attach_invalidtoken.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/attach_invalidtoken.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/attach_invalidtoken.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,54 +1,56 @@ Feature: Command behaviour when trying to attach a machine to an Ubuntu - Pro subscription using an invalid token + Pro subscription using an invalid token - Scenario Outline: Attach command failure on invalid token - Given a `` `` machine with ubuntu-advantage-tools installed - When I verify that running `pro attach INVALID_TOKEN` `with sudo` exits `1` - Then stderr matches regexp: - """ - Invalid token. See https://ubuntu.com/pro - """ - When I verify that running `pro attach INVALID_TOKEN` `as non-root` exits `1` - Then I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - When I verify that running `pro attach invalid-token --format json` `with sudo` exits `1` - Then stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "Invalid token. See https://ubuntu.com/pro/dashboard", "message_code": "attach-invalid-token", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ + Scenario Outline: Attach command failure on invalid token + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify that running `pro attach INVALID_TOKEN` `with sudo` exits `1` + Then stderr matches regexp: + """ + Invalid token. See https://ubuntu.com/pro + """ + When I verify that running `pro attach INVALID_TOKEN` `as non-root` exits `1` + Then I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + When I verify that running `pro attach invalid-token --format json` `with sudo` exits `1` + Then stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "Invalid token. See https://ubuntu.com/pro/dashboard", "message_code": "attach-invalid-token", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | - @uses.config.contract_token_staging_expired - Scenario Outline: Attach command failure on expired token - Given a `` `` machine with ubuntu-advantage-tools installed - When I attempt to attach `contract_token_staging_expired` with sudo - Then stderr matches regexp: - """ - Attach denied: - Contract ".*" .* - Visit https://ubuntu.com/pro/dashboard to manage contract tokens. - """ - When I verify that running attach `with sudo` using expired token with json response fails - Then stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"additional_info": {"contract_expiry_date": "08-21-2022", "contract_id": "cAHT7ADjWMRCjo5Q53QlTawtPlrhxeRg7cbEnquxxm1g", "date": "August 21, 2022"}, "message": "Attach denied:\nContract \"cAHT7ADjWMRCjo5Q53QlTawtPlrhxeRg7cbEnquxxm1g\" expired on August 21, 2022\nVisit https://ubuntu.com/pro/dashboard to manage contract tokens.", "message_code": "attach-forbidden-expired", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ + @uses.config.contract_token_staging_expired + Scenario Outline: Attach command failure on expired token + Given a `` `` machine with ubuntu-advantage-tools installed + When I attempt to attach `contract_token_staging_expired` with sudo + Then stderr matches regexp: + """ + Attach denied: + Contract ".*" .* + Visit https://ubuntu.com/pro/dashboard to manage contract tokens. + """ + When I verify that running attach `with sudo` using expired token with json response fails + Then stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"additional_info": {"contract_expiry_date": "08-21-2022", "contract_id": "cAHT7ADjWMRCjo5Q53QlTawtPlrhxeRg7cbEnquxxm1g", "date": "August 21, 2022"}, "message": "Attach denied:\nContract \"cAHT7ADjWMRCjo5Q53QlTawtPlrhxeRg7cbEnquxxm1g\" expired on August 21, 2022\nVisit https://ubuntu.com/pro/dashboard to manage contract tokens.", "message_code": "attach-forbidden-expired", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/attach_validtoken.feature ubuntu-advantage-tools-32~16.04/features/attach_validtoken.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/attach_validtoken.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/attach_validtoken.feature 2024-05-10 17:07:05.000000000 +0000 @@ -1,280 +1,354 @@ @uses.config.contract_token Feature: Command behaviour when attaching a machine to an Ubuntu Pro - subscription using a valid token + subscription using a valid token - Scenario Outline: Attached command in a non-lts ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `pro status` as non-root - Then stdout matches regexp: - """ - - """ - And stdout matches regexp: - """ - For a list of all Ubuntu Pro services, run 'pro status --all' - """ - When I run `pro status --all` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +yes +n/a +.* - cc-eal +yes +n/a +Common Criteria EAL2 Provisioning Packages - cis +yes +n/a +Security compliance and audit tools - esm-apps +yes +n/a +Expanded Security Maintenance for Applications - esm-infra +yes +n/a +Expanded Security Maintenance for Infrastructure - fips +yes +n/a +NIST-certified FIPS crypto packages - fips-preview +yes +n/a +.* - fips-updates +yes +n/a +FIPS compliant crypto packages with stable security updates - landscape +yes + +Management and administration tool for Ubuntu - livepatch +yes +n/a +Canonical Livepatch service - """ - And stdout does not match regexp: - """ - For a list of all Ubuntu Pro services, run 'pro status --all' - """ - - Examples: ubuntu release - | release | machine_type | landscape | status_string | - | mantic | lxd-container | disabled | landscape +yes +disabled +Management and administration tool for Ubuntu | - - Scenario Outline: Attach command in a ubuntu lxd container - Given a `` `` machine with ubuntu-advantage-tools installed - When I apt update - And I apt install `update-motd` - And I apt install `` - And I run `pro refresh messages` with sudo - Then stdout matches regexp: - """ - Successfully updated Ubuntu Pro related APT and MOTD messages. - """ - When I run `update-motd` with sudo - Then if `` in `xenial` and stdout matches regexp: - """ - \d+ update(s)? can be applied immediately. - \d+ of these updates (is a|are) standard security update(s)?. - """ - Then if `` in `bionic` and stdout matches regexp: - """ - \d+ update(s)? can be applied immediately. - \d+ of these updates (is a|are) standard security update(s)?. - """ - Then if `` in `focal` and stdout matches regexp: - """ - \d+ update(s)? can be applied immediately. - """ - When I attach `contract_token` with sudo - Then stdout matches regexp: - """ - Ubuntu Pro: ESM Infra enabled - """ - And stdout matches regexp: - """ - This machine is now attached to - """ - And stderr matches regexp: - """ - Enabling default service esm-infra - """ - Then I verify that `esm-infra` is enabled - And I verify that `esm-apps` is enabled - When I verify that running `pro attach contract_token` `with sudo` exits `2` - Then stderr matches regexp: - """ - This machine is already attached to '.+' - To use a different subscription first run: sudo pro detach. - """ - - Examples: ubuntu release packages - | release | machine_type | downrev_pkg | cc_status | cis_or_usg | cis | fips | livepatch_desc | - | xenial | lxd-container | libkrad0=1.13.2+dfsg-5 | disabled | cis | disabled | disabled | Canonical Livepatch service | - | bionic | lxd-container | libkrad0=1.16-2build1 | disabled | cis | disabled | disabled | Canonical Livepatch service | - | focal | lxd-container | hello=2.10-2ubuntu2 | n/a | usg | disabled | disabled | Canonical Livepatch service | - | jammy | lxd-container | hello=2.10-2ubuntu4 | n/a | usg | n/a | n/a | Canonical Livepatch service | - - Scenario Outline: Attach command with attach config - Given a `` `` machine with ubuntu-advantage-tools installed - # simplest happy path - When I create the file `/tmp/attach.yaml` with the following - """ - token: - """ - When I replace `` in `/tmp/attach.yaml` with token `contract_token` - When I run `pro attach --attach-config /tmp/attach.yaml` with sudo - Then I verify that `esm-apps` is enabled - And I verify that `esm-infra` is enabled - And I verify that `` is disabled - When I run `pro detach --assume-yes` with sudo - # don't allow both token on cli and config - Then I verify that running `pro attach TOKEN --attach-config /tmp/attach.yaml` `with sudo` exits `1` - Then stderr matches regexp: - """ - Do not pass the TOKEN arg if you are using --attach-config. - Include the token in the attach-config file instead. - """ - # happy path with service overrides - When I create the file `/tmp/attach.yaml` with the following - """ - token: - enable_services: - - esm-apps - - - """ - When I replace `` in `/tmp/attach.yaml` with token `contract_token` - When I run `pro attach --attach-config /tmp/attach.yaml` with sudo - Then I verify that `esm-apps` is enabled - And I verify that `esm-infra` is disabled - And I verify that `` is enabled - When I run `pro detach --assume-yes` with sudo - # missing token - When I create the file `/tmp/attach.yaml` with the following - """ - enable_services: - - esm-apps - - - """ - Then I verify that running `pro attach --attach-config /tmp/attach.yaml` `with sudo` exits `1` - Then stderr matches regexp: - """ - Error while reading /tmp/attach.yaml: - Got value with incorrect type for field "token": - Expected value with type StringDataValue but got type: null - """ - # other schema error - When I create the file `/tmp/attach.yaml` with the following - """ - token: - enable_services: {cis: true} - """ - When I replace `` in `/tmp/attach.yaml` with token `contract_token` - Then I verify that running `pro attach --attach-config /tmp/attach.yaml` `with sudo` exits `1` - Then stderr matches regexp: - """ - Error while reading /tmp/attach.yaml: - Got value with incorrect type for field "enable_services": - Expected value with type list but got type: dict - """ - # invalid service name - When I create the file `/tmp/attach.yaml` with the following - """ - token: - enable_services: - - esm-apps - - nonexistent - - nonexistent2 - """ - When I replace `` in `/tmp/attach.yaml` with token `contract_token` - Then I verify that running `pro attach --attach-config /tmp/attach.yaml` `with sudo` exits `1` - And stderr matches regexp: - """ - Cannot enable unknown service 'nonexistent, nonexistent2'. - """ - And I verify that `esm-apps` is enabled - And I verify that `esm-infra` is disabled - - Examples: ubuntu - | release | machine_type | cis_or_usg | - | xenial | lxd-container | cis | - | bionic | lxd-container | cis | - | focal | lxd-container | usg | - - Scenario Outline: Attach command in an generic cloud images - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then stdout matches regexp: - """ - Ubuntu Pro: ESM Infra enabled - """ - And stdout matches regexp: - """ - This machine is now attached to - """ - And stderr matches regexp: - """ - Enabling default service esm-infra - """ - And I verify that `esm-infra` is enabled - - Examples: ubuntu release livepatch status - | release | machine_type | - | xenial | aws.generic | - | xenial | azure.generic | - | xenial | gcp.generic | - | bionic | aws.generic | - | bionic | azure.generic | - | bionic | gcp.generic | - | focal | aws.generic | - | focal | azure.generic | - | focal | gcp.generic | - | jammy | aws.generic | - | jammy | azure.generic | - | jammy | gcp.generic | - - Scenario Outline: Attach command with json output - Given a `` `` machine with ubuntu-advantage-tools installed - When I verify that running attach `as non-root` with json response exits `1` - Then I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "This command must be run as root (try using sudo).", "message_code": "nonroot-user", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - When I verify that running attach `with sudo` with json response exits `0` - Then I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-apps", "esm-infra"], "result": "success", "warnings": []} - """ - And I verify that `esm-infra` is enabled - And I verify that `esm-apps` is enabled - - Examples: ubuntu release - | release | machine_type | cc-eal | - | xenial | lxd-container | disabled | - | bionic | lxd-container | disabled | - | focal | lxd-container | n/a | - | jammy | lxd-container | n/a | - - Scenario Outline: Attach and Check for contract change in status checking - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then stdout matches regexp: - """ - Ubuntu Pro: ESM Infra enabled - """ - And stdout matches regexp: - """ - This machine is now attached to - """ - And I verify that `esm-infra` is enabled - When I set the machine token overlay to the following yaml - """ - machineTokenInfo: - contractInfo: - effectiveTo: 2000-01-02T03:04:05Z - """ - And I delete the file `/var/lib/ubuntu-advantage/jobs-status.json` - And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo - And I run `pro status` with sudo - Then stdout matches regexp: - """ - A change has been detected in your contract. - Please run `sudo pro refresh`. - """ - When I run `pro refresh contract` with sudo - Then stdout matches regexp: - """ - Successfully refreshed your subscription. - """ - # remove machine token overlay - When I change config key `features` to use value `{}` - And I run `pro status` with sudo - Then stdout does not match regexp: - """ - A change has been detected in your contract. - Please run `sudo pro refresh`. - """ - - Examples: ubuntu release livepatch status - | release | machine_type | - # removing until we add this feature back in a way that doesn't hammer the server - #| xenial | lxd-container | - #| bionic | lxd-container | - #| focal | lxd-container | + Scenario Outline: Attached command in a non-lts ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `pro status` as non-root + Then stdout matches regexp: + """ + + """ + And stdout matches regexp: + """ + For a list of all Ubuntu Pro services, run 'pro status --all' + """ + When I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +n/a +.* + cc-eal +yes +n/a +Common Criteria EAL2 Provisioning Packages + cis +yes +n/a +Security compliance and audit tools + esm-apps +yes +n/a +Expanded Security Maintenance for Applications + esm-infra +yes +n/a +Expanded Security Maintenance for Infrastructure + fips +yes +n/a +NIST-certified FIPS crypto packages + fips-preview +yes +n/a +.* + fips-updates +yes +n/a +FIPS compliant crypto packages with stable security updates + landscape +yes + +Management and administration tool for Ubuntu + livepatch +yes +n/a +Canonical Livepatch service + """ + And stdout does not match regexp: + """ + For a list of all Ubuntu Pro services, run 'pro status --all' + """ + + Examples: ubuntu release + | release | machine_type | landscape | status_string | + | mantic | lxd-container | disabled | landscape +yes +disabled +Management and administration tool for Ubuntu | + + Scenario Outline: Attach command in a ubuntu lxd container + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt install `update-motd` + And I apt install `` + And I run `pro refresh messages` with sudo + Then stdout matches regexp: + """ + Successfully updated Ubuntu Pro related APT and MOTD messages. + """ + When I run `update-motd` with sudo + Then if `` in `xenial` and stdout matches regexp: + """ + \d+ update(s)? can be applied immediately. + \d+ of these updates (is a|are) standard security update(s)?. + """ + Then if `` in `bionic` and stdout matches regexp: + """ + \d+ update(s)? can be applied immediately. + \d+ of these updates (is a|are) standard security update(s)?. + """ + Then if `` in `focal` and stdout matches regexp: + """ + \d+ update(s)? can be applied immediately. + """ + When I attach `contract_token` with sudo + Then I verify that `esm-infra` is enabled + And I verify that `esm-apps` is enabled + When I verify that running `pro attach contract_token` `with sudo` exits `2` + Then stderr matches regexp: + """ + This machine is already attached to '.+' + To use a different subscription first run: sudo pro detach. + """ + And I verify that `/var/lib/ubuntu-advantage/status.json` is owned by `root:root` with permission `644` + + Examples: ubuntu release packages + | release | machine_type | downrev_pkg | cc_status | cis_or_usg | cis | fips | livepatch_desc | + | xenial | lxd-container | libkrad0=1.13.2+dfsg-5 | disabled | cis | disabled | disabled | Canonical Livepatch service | + | bionic | lxd-container | libkrad0=1.16-2build1 | disabled | cis | disabled | disabled | Canonical Livepatch service | + | focal | lxd-container | hello=2.10-2ubuntu2 | n/a | usg | disabled | disabled | Canonical Livepatch service | + | jammy | lxd-container | hello=2.10-2ubuntu4 | n/a | usg | n/a | n/a | Canonical Livepatch service | + | noble | lxd-container | hello=2.10-3build1 | n/a | usg | n/a | n/a | Canonical Livepatch service | + + Scenario Outline: Attach command with attach config + Given a `` `` machine with ubuntu-advantage-tools installed + # simplest happy path + When I create the file `/tmp/attach.yaml` with the following + """ + token: + """ + When I replace `` in `/tmp/attach.yaml` with token `contract_token` + When I run `pro attach --attach-config /tmp/attach.yaml` with sudo + Then I verify that `esm-apps` is enabled + And I verify that `esm-infra` is enabled + And I verify that `` is disabled + When I run `pro detach --assume-yes` with sudo + # don't allow both token on cli and config + Then I verify that running `pro attach TOKEN --attach-config /tmp/attach.yaml` `with sudo` exits `1` + Then stderr matches regexp: + """ + Do not pass the TOKEN arg if you are using --attach-config. + Include the token in the attach-config file instead. + """ + # happy path with service overrides + When I create the file `/tmp/attach.yaml` with the following + """ + token: + enable_services: + - esm-apps + - + """ + When I replace `` in `/tmp/attach.yaml` with token `contract_token` + When I run `pro attach --attach-config /tmp/attach.yaml` with sudo + Then I verify that `esm-apps` is enabled + And I verify that `esm-infra` is disabled + And I verify that `` is enabled + When I run `pro detach --assume-yes` with sudo + # missing token + When I create the file `/tmp/attach.yaml` with the following + """ + enable_services: + - esm-apps + - + """ + Then I verify that running `pro attach --attach-config /tmp/attach.yaml` `with sudo` exits `1` + Then stderr matches regexp: + """ + Error while reading /tmp/attach.yaml: + Got value with incorrect type for field "token": + Expected value with type StringDataValue but got type: null + """ + # other schema error + When I create the file `/tmp/attach.yaml` with the following + """ + token: + enable_services: {cis: true} + """ + When I replace `` in `/tmp/attach.yaml` with token `contract_token` + Then I verify that running `pro attach --attach-config /tmp/attach.yaml` `with sudo` exits `1` + Then stderr matches regexp: + """ + Error while reading /tmp/attach.yaml: + Got value with incorrect type for field "enable_services": + Expected value with type list but got type: dict + """ + # invalid service name + When I create the file `/tmp/attach.yaml` with the following + """ + token: + enable_services: + - esm-apps + - nonexistent + - nonexistent2 + """ + When I replace `` in `/tmp/attach.yaml` with token `contract_token` + Then I verify that running `pro attach --attach-config /tmp/attach.yaml` `with sudo` exits `1` + And stderr matches regexp: + """ + Cannot enable unknown service 'nonexistent, nonexistent2'. + """ + And I verify that `esm-apps` is enabled + And I verify that `esm-infra` is disabled + + Examples: ubuntu + | release | machine_type | cis_or_usg | + | xenial | lxd-container | cis | + | bionic | lxd-container | cis | + | focal | lxd-container | usg | + + Scenario Outline: Auto enable by default and attached disable of livepatch in a lxd vm + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that `esm-infra` is enabled + And I verify that `esm-apps` is enabled + And I verify that `livepatch` status is `` + When I run `pro disable livepatch` with sudo + Then I verify that running `canonical-livepatch status` `with sudo` exits `1` + And stderr matches regexp: + """ + Machine is not enabled. Please run 'sudo canonical-livepatch enable' with the + token obtained from https://ubuntu.com/livepatch. + """ + And I verify that `livepatch` is disabled + When I verify that running `pro enable livepatch --access-only` `with sudo` exits `1` + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Livepatch does not support being enabled with --access-only + Could not enable Livepatch. + """ + + Examples: ubuntu release + | release | machine_type | livepatch_status | + | xenial | lxd-vm | warning | + | bionic | lxd-vm | enabled | + | focal | lxd-vm | enabled | + | jammy | lxd-vm | enabled | + + Scenario Outline: Attach command in an generic cloud images + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then stdout matches regexp: + """ + Ubuntu Pro: ESM Infra enabled + """ + And stdout matches regexp: + """ + This machine is now attached to + """ + And I verify that `esm-infra` is enabled + + Examples: ubuntu release livepatch status + | release | machine_type | + | xenial | aws.generic | + | xenial | azure.generic | + | xenial | gcp.generic | + | bionic | aws.generic | + | bionic | azure.generic | + | bionic | gcp.generic | + | focal | aws.generic | + | focal | azure.generic | + | focal | gcp.generic | + | jammy | aws.generic | + | jammy | azure.generic | + | jammy | gcp.generic | + | noble | aws.generic | + | noble | azure.generic | + | noble | gcp.generic | + + Scenario Outline: Attach command with json output + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify that running attach `as non-root` with json response exits `1` + Then I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "This command must be run as root (try using sudo).", "message_code": "nonroot-user", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + When I verify that running attach `with sudo` with json response exits `0` + Then I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-apps", "esm-infra"], "result": "success", "warnings": []} + """ + And I verify that `esm-infra` is enabled + And I verify that `esm-apps` is enabled + + Examples: ubuntu release + | release | machine_type | cc-eal | + | xenial | lxd-container | disabled | + | bionic | lxd-container | disabled | + | focal | lxd-container | n/a | + | jammy | lxd-container | n/a | + | noble | lxd-container | n/a | + + Scenario Outline: Attach and Check for contract change in status checking + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then stdout matches regexp: + """ + Ubuntu Pro: ESM Infra enabled + """ + And stdout matches regexp: + """ + This machine is now attached to + """ + And I verify that `esm-infra` is enabled + When I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + effectiveTo: 2000-01-02T03:04:05Z + """ + And I delete the file `/var/lib/ubuntu-advantage/jobs-status.json` + And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo + And I run `pro status` with sudo + Then stdout matches regexp: + """ + A change has been detected in your contract. + Please run `sudo pro refresh`. + """ + When I run `pro refresh contract` with sudo + Then stdout matches regexp: + """ + Successfully refreshed your subscription. + """ + # remove machine token overlay + When I change config key `features` to use value `{}` + And I run `pro status` with sudo + Then stdout does not match regexp: + """ + A change has been detected in your contract. + Please run `sudo pro refresh`. + """ + + Examples: ubuntu release livepatch status + | release | machine_type | + + # removing until we add this feature back in a way that doesn't hammer the server + # | xenial | lxd-container | + # | bionic | lxd-container | + # | focal | lxd-container | + Scenario Outline: Attach and Check for contract change in status checking + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/tmp/response-overlay.json` with the following: + """ + { + "https://contracts.canonical.com/v1/context/machines/token": [ + { + "code": 200, + "response": { + "machineTokenInfo": { + "contractInfo": { + "resourceEntitlements": [ + { + "type": "esm-infra", + "directives": { + "aptURL": "test", + "suites": [""] + } + }, + { + "type": "esm-apps", + "directives": { + "aptURL": "test", + "suites": [""] + } + } + ] + } + } + } + }] + } + """ + And I append the following on uaclient config: + """ + features: + serviceclient_url_responses: "/tmp/response-overlay.json" + """ + And I verify that running `pro attach TOKEN` `with sudo` exits `1` + Then I will see the following on stderr: + """ + There is a problem with the resource directives provided by https://contracts.canonical.com + These entitlements: esm-apps, esm-infra are sharing the following directives + - APT url: test + - Suite: + These directives need to be unique for every entitlement. + """ + And the machine is unattached + + Examples: ubuntu release livepatch status + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/attached_commands.feature ubuntu-advantage-tools-32~16.04/features/attached_commands.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/attached_commands.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/attached_commands.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,906 +1,928 @@ @uses.config.contract_token Feature: Command behaviour when attached to an Ubuntu Pro subscription - Scenario Outline: Attached refresh in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I verify that `Bearer ` field is redacted in the logs - And I verify that `'attach', '` field is redacted in the logs - And I verify that `'machineToken': '` field is redacted in the logs - Then I verify that running `pro refresh` `as non-root` exits `1` - And stderr matches regexp: - """ - This command must be run as root \(try using sudo\). - """ - When I run `pro refresh` with sudo - Then I will see the following on stdout: - """ - Successfully processed your pro configuration. - Successfully refreshed your subscription. - Successfully updated Ubuntu Pro related APT and MOTD messages. - """ - When I run `pro refresh config` with sudo - Then I will see the following on stdout: - """ - Successfully processed your pro configuration. - """ - When I run `pro refresh contract` with sudo - Then I will see the following on stdout: - """ - Successfully refreshed your subscription. - """ - When I run `pro refresh messages` with sudo - Then I will see the following on stdout: - """ - Successfully updated Ubuntu Pro related APT and MOTD messages. - """ - - Examples: ubuntu release - | release | machine_type | - | bionic | lxd-container | - | focal | lxd-container | - | xenial | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - Scenario Outline: Disable command on an attached machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that running `pro disable livepatch` `as non-root` exits `1` - And stderr matches regexp: - """ - This command must be run as root \(try using sudo\). - """ - When I verify that running `pro disable foobar` `as non-root` exits `1` - Then stderr matches regexp: - """ - This command must be run as root \(try using sudo\). - """ - When I verify that running `pro disable livepatch` `with sudo` exits `1` - Then I will see the following on stdout: - """ - Livepatch is not currently enabled - See: sudo pro status - """ - When I verify that running `pro disable foobar` `with sudo` exits `1` - Then stderr matches regexp: - """ - Cannot disable unknown service 'foobar'. - - """ - When I verify that running `pro disable livepatch foobar` `as non-root` exits `1` - Then stderr matches regexp: - """ - This command must be run as root \(try using sudo\) - """ - When I verify that running `pro disable livepatch foobar` `with sudo` exits `1` - Then I will see the following on stdout: - """ - Livepatch is not currently enabled - See: sudo pro status - """ - And stderr matches regexp: - """ - Cannot disable unknown service 'foobar'. - - """ - When I verify that running `pro disable esm-infra` `as non-root` exits `1` - Then stderr matches regexp: - """ - This command must be run as root \(try using sudo\). - """ - When I run `pro disable esm-infra` with sudo - Then I verify that `esm-infra` is disabled - And I verify that running `apt update` `with sudo` exits `0` - - Examples: ubuntu release - | release | machine_type | msg | - | xenial | lxd-container | Try anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | - | bionic | lxd-container | Try anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | - | focal | lxd-container | Try anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | - | jammy | lxd-container | Try anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | - - Scenario Outline: Attached disable with json format - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that running `pro disable foobar --format json` `as non-root` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - Then I verify that running `pro disable foobar --format json` `with sudo` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - Then I verify that running `pro disable foobar --format json --assume-yes` `as non-root` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "This command must be run as root (try using sudo).", "message_code": "nonroot-user", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - And I verify that running `pro disable foobar --format json --assume-yes` `with sudo` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"additional_info": {"invalid_service": "foobar", "operation": "disable", "service_msg": "Try "}, "message": "Cannot disable unknown service 'foobar'.\nTry ", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - And I verify that running `pro disable livepatch --format json --assume-yes` `with sudo` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "Livepatch is not currently enabled\nSee: sudo pro status", "message_code": "service-already-disabled", "service": "livepatch", "type": "service"}], "failed_services": ["livepatch"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - And I verify that running `pro disable esm-infra esm-apps --format json --assume-yes` `with sudo` exits `0` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-apps", "esm-infra"], "result": "success", "warnings": []} - """ - When I run `pro enable esm-infra` with sudo - Then I verify that running `pro disable esm-infra foobar --format json --assume-yes` `with sudo` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"additional_info": {"invalid_service": "foobar", "operation": "disable", "service_msg": "Try "}, "message": "Cannot disable unknown service 'foobar'.\nTry ", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-infra"], "result": "failure", "warnings": []} - """ - - Examples: ubuntu release - | release | machine_type | valid_services | - | xenial | lxd-container | anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | - | bionic | lxd-container | anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | - | focal | lxd-container | anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | - | jammy | lxd-container | anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | - - Scenario Outline: Attached detach in an ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I verify that `Bearer ` field is redacted in the logs - And I verify that `'attach', '` field is redacted in the logs - And I verify that `'machineToken': '` field is redacted in the logs - And I run `pro api u.pro.status.enabled_services.v1` as non-root - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"enabled_services": \[{"name": "esm-apps", "variant_enabled": false, "variant_name": null}, {"name": "esm-infra", "variant_enabled": false, "variant_name": null}\]}, "meta": {"environment_vars": \[\]}, "type": "EnabledServices"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - Then I verify that running `pro detach` `as non-root` exits `1` - And stderr matches regexp: - """ - This command must be run as root \(try using sudo\). - """ - When I run `pro detach --assume-yes` with sudo - Then I will see the following on stdout: - """ - Detach will disable the following services: - esm-apps - esm-infra - Updating package lists - Updating package lists - This machine is now detached. - """ - And the machine is unattached - And I ensure apt update runs without errors - When I attach `contract_token` with sudo - Then I verify that running `pro enable foobar --format json` `as non-root` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - Then I verify that running `pro enable foobar --format json` `with sudo` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - Then I verify that running `pro detach --format json --assume-yes` `as non-root` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "This command must be run as root (try using sudo).", "message_code": "nonroot-user", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - When I run `pro detach --format json --assume-yes` with sudo - Then stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-apps", "esm-infra"], "result": "success", "warnings": []} - """ - And the machine is unattached - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - Scenario Outline: Attached auto-attach in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that running `pro auto-attach` `as non-root` exits `1` - And stderr matches regexp: - """ - This command must be run as root \(try using sudo\). - """ - When I verify that running `pro auto-attach` `with sudo` exits `2` - Then stderr matches regexp: - """ - This machine is already attached to '.+' - To use a different subscription first run: sudo pro detach. - """ - - Examples: ubuntu release - | release | machine_type | - | bionic | lxd-container | - | focal | lxd-container | - | xenial | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - Scenario Outline: Attached show version in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `pro version` as non-root - Then I will see the uaclient version on stdout - When I run `pro version` with sudo - Then I will see the uaclient version on stdout - When I run `pro --version` as non-root - Then I will see the uaclient version on stdout - When I run `pro --version` with sudo - Then I will see the uaclient version on stdout - - Examples: ubuntu release - | release | machine_type | - | bionic | lxd-container | - | focal | lxd-container | - | xenial | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - Scenario Outline: Attached status in a ubuntu machine with feature overrides - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/var/lib/ubuntu-advantage/machine-token-overlay.json` with the following: - """ - { - "machineTokenInfo": { - "contractInfo": { - "resourceEntitlements": [ - { - "type": "cc-eal", - "entitled": false - } - ] - } - } - } - """ - And I append the following on uaclient config: - """ - features: - machine_token_overlay: "/var/lib/ubuntu-advantage/machine-token-overlay.json" - disable_auto_attach: true - other: false - """ - And I attach `contract_token` with sudo - And I run `pro status --all` with sudo - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +.* - cc-eal +no - """ - And stdout matches regexp: - """ - FEATURES - disable_auto_attach: True - machine_token_overlay: /var/lib/ubuntu-advantage/machine-token-overlay.json - other: False - """ - When I run `pro status --all` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +.* - cc-eal +no - """ - And stdout matches regexp: - """ - FEATURES - disable_auto_attach: True - machine_token_overlay: /var/lib/ubuntu-advantage/machine-token-overlay.json - other: False - """ - When I run `pro detach --assume-yes` with sudo - Then I verify that running `pro auto-attach` `with sudo` exits `1` - Then stderr matches regexp: - """ - features.disable_auto_attach set in config - """ - - Examples: ubuntu release - | release | machine_type | - | bionic | lxd-container | - | focal | lxd-container | - | xenial | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - Scenario Outline: Attached enable when reboot required - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `pro disable esm-infra` with sudo - And I run `touch /var/run/reboot-required` with sudo - And I run `touch /var/run/reboot-required.pkgs` with sudo - And I run `pro enable esm-infra` with sudo - Then stdout matches regexp: - """ - Updating Ubuntu Pro: ESM Infra package lists - Ubuntu Pro: ESM Infra enabled - """ - And stdout does not match regexp: - """ - A reboot is required to complete install. - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - Scenario Outline: Help command on an attached machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `pro help esm-infra` with sudo - Then I will see the following on stdout: - """ - Name: - esm-infra - - Entitled: - yes - - Status: - - - Help: - Expanded Security Maintenance for Infrastructure provides access to a private - PPA which includes available high and critical CVE fixes for Ubuntu LTS - packages in the Ubuntu Main repository between the end of the standard Ubuntu - LTS security maintenance and its end of life. It is enabled by default with - Ubuntu Pro. You can find out more about the service at - https://ubuntu.com/security/esm - """ - When I run `pro help esm-infra --format json` with sudo - Then I will see the following on stdout: - """ - {"name": "esm-infra", "entitled": "yes", "status": "", "help": "Expanded Security Maintenance for Infrastructure provides access to a private\nPPA which includes available high and critical CVE fixes for Ubuntu LTS\npackages in the Ubuntu Main repository between the end of the standard Ubuntu\nLTS security maintenance and its end of life. It is enabled by default with\nUbuntu Pro. You can find out more about the service at\nhttps://ubuntu.com/security/esm"} - """ - And I verify that running `pro help invalid-service` `with sudo` exits `1` - And I will see the following on stderr: - """ - No help available for 'invalid-service' - """ - When I run `pro --help` as non-root - Then stdout matches regexp: - """ - Client to manage Ubuntu Pro services on a machine. - - anbox-cloud: .* - - cc-eal: Common Criteria EAL2 Provisioning Packages - \(https://ubuntu.com/security/cc\) - - cis: Security compliance and audit tools - \(https://ubuntu.com/security/certifications/docs/usg\) - - esm-apps: Expanded Security Maintenance for Applications - \(https://ubuntu.com/security/esm\) - - esm-infra: Expanded Security Maintenance for Infrastructure - \(https://ubuntu.com/security/esm\) - - fips-preview: .* - .*\(https://ubuntu.com/security/fips\) - - fips-updates: FIPS compliant crypto packages with stable security updates - \(https://ubuntu.com/security/fips\) - - fips: NIST-certified FIPS crypto packages \(https://ubuntu.com/security/fips\) - - landscape: Management and administration tool for Ubuntu - \(https://ubuntu.com/landscape\) - - livepatch: Canonical Livepatch service - \(https://ubuntu.com/security/livepatch\) - """ - When I run `pro help` with sudo - Then stdout matches regexp: - """ - Client to manage Ubuntu Pro services on a machine. - - anbox-cloud: .* - - cc-eal: Common Criteria EAL2 Provisioning Packages - \(https://ubuntu.com/security/cc\) - - cis: Security compliance and audit tools - \(https://ubuntu.com/security/certifications/docs/usg\) - - esm-apps: Expanded Security Maintenance for Applications - \(https://ubuntu.com/security/esm\) - - esm-infra: Expanded Security Maintenance for Infrastructure - \(https://ubuntu.com/security/esm\) - - fips-preview: .* - .*\(https://ubuntu.com/security/fips\) - - fips-updates: FIPS compliant crypto packages with stable security updates - \(https://ubuntu.com/security/fips\) - - fips: NIST-certified FIPS crypto packages \(https://ubuntu.com/security/fips\) - - landscape: Management and administration tool for Ubuntu - \(https://ubuntu.com/landscape\) - - livepatch: Canonical Livepatch service - \(https://ubuntu.com/security/livepatch\) - """ - When I run `pro help --all` as non-root - Then stdout matches regexp: - """ - Client to manage Ubuntu Pro services on a machine. - - anbox-cloud: .* - - cc-eal: Common Criteria EAL2 Provisioning Packages - \(https://ubuntu.com/security/cc\) - - cis: Security compliance and audit tools - \(https://ubuntu.com/security/certifications/docs/usg\) - - esm-apps: Expanded Security Maintenance for Applications - \(https://ubuntu.com/security/esm\) - - esm-infra: Expanded Security Maintenance for Infrastructure - \(https://ubuntu.com/security/esm\) - - fips-preview: .* - .*\(https://ubuntu.com/security/fips\) - - fips-updates: FIPS compliant crypto packages with stable security updates - \(https://ubuntu.com/security/fips\) - - fips: NIST-certified FIPS crypto packages \(https://ubuntu.com/security/fips\) - - landscape: Management and administration tool for Ubuntu - \(https://ubuntu.com/landscape\) - - livepatch: Canonical Livepatch service - \(https://ubuntu.com/security/livepatch\) - - realtime-kernel: Ubuntu kernel with PREEMPT_RT patches integrated - \(https://ubuntu.com/realtime-kernel\) - - ros-updates: All Updates for the Robot Operating System - \(https://ubuntu.com/robotics/ros-esm\) - - ros: Security Updates for the Robot Operating System - \(https://ubuntu.com/robotics/ros-esm\) - """ - - Examples: ubuntu release - | release | machine_type | infra-status | - | bionic | lxd-container | enabled | - | xenial | lxd-container | enabled | - | mantic | lxd-container | n/a | - - Scenario Outline: Help command on an attached machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `pro help esm-infra` with sudo - Then I will see the following on stdout: - """ - Name: - esm-infra - - Entitled: - yes - - Status: - enabled - - Help: - Expanded Security Maintenance for Infrastructure provides access to a private - PPA which includes available high and critical CVE fixes for Ubuntu LTS - packages in the Ubuntu Main repository between the end of the standard Ubuntu - LTS security maintenance and its end of life. It is enabled by default with - Ubuntu Pro. You can find out more about the service at - https://ubuntu.com/security/esm - """ - When I run `pro help esm-infra --format json` with sudo - Then I will see the following on stdout: - """ - {"name": "esm-infra", "entitled": "yes", "status": "enabled", "help": "Expanded Security Maintenance for Infrastructure provides access to a private\nPPA which includes available high and critical CVE fixes for Ubuntu LTS\npackages in the Ubuntu Main repository between the end of the standard Ubuntu\nLTS security maintenance and its end of life. It is enabled by default with\nUbuntu Pro. You can find out more about the service at\nhttps://ubuntu.com/security/esm"} - """ - And I verify that running `pro help invalid-service` `with sudo` exits `1` - And I will see the following on stderr: - """ - No help available for 'invalid-service' - """ - When I run `pro --help` as non-root - Then stdout matches regexp: - """ - Client to manage Ubuntu Pro services on a machine. - - anbox-cloud: .* - - cc-eal: Common Criteria EAL2 Provisioning Packages - \(https://ubuntu.com/security/cc\) - - esm-apps: Expanded Security Maintenance for Applications - \(https://ubuntu.com/security/esm\) - - esm-infra: Expanded Security Maintenance for Infrastructure - \(https://ubuntu.com/security/esm\) - - fips-preview: .* - .*\(https://ubuntu.com/security/fips\) - - fips-updates: FIPS compliant crypto packages with stable security updates - \(https://ubuntu.com/security/fips\) - - fips: NIST-certified FIPS crypto packages \(https://ubuntu.com/security/fips\) - - landscape: Management and administration tool for Ubuntu - \(https://ubuntu.com/landscape\) - - livepatch: Canonical Livepatch service - \(https://ubuntu.com/security/livepatch\) - - realtime-kernel: Ubuntu kernel with PREEMPT_RT patches integrated - \(https://ubuntu.com/realtime-kernel\) - - ros-updates: All Updates for the Robot Operating System - \(https://ubuntu.com/robotics/ros-esm\) - - ros: Security Updates for the Robot Operating System - \(https://ubuntu.com/robotics/ros-esm\) - - usg: Security compliance and audit tools - \(https://ubuntu.com/security/certifications/docs/usg\) - """ - When I run `pro help` with sudo - Then stdout matches regexp: - """ - Client to manage Ubuntu Pro services on a machine. - - anbox-cloud: .* - - cc-eal: Common Criteria EAL2 Provisioning Packages - \(https://ubuntu.com/security/cc\) - - esm-apps: Expanded Security Maintenance for Applications - \(https://ubuntu.com/security/esm\) - - esm-infra: Expanded Security Maintenance for Infrastructure - \(https://ubuntu.com/security/esm\) - - fips-preview: .* - .*\(https://ubuntu.com/security/fips\) - - fips-updates: FIPS compliant crypto packages with stable security updates - \(https://ubuntu.com/security/fips\) - - fips: NIST-certified FIPS crypto packages \(https://ubuntu.com/security/fips\) - - landscape: Management and administration tool for Ubuntu - \(https://ubuntu.com/landscape\) - - livepatch: Canonical Livepatch service - \(https://ubuntu.com/security/livepatch\) - - realtime-kernel: Ubuntu kernel with PREEMPT_RT patches integrated - \(https://ubuntu.com/realtime-kernel\) - - ros-updates: All Updates for the Robot Operating System - \(https://ubuntu.com/robotics/ros-esm\) - - ros: Security Updates for the Robot Operating System - \(https://ubuntu.com/robotics/ros-esm\) - - usg: Security compliance and audit tools - \(https://ubuntu.com/security/certifications/docs/usg\) - """ - When I run `pro help --all` as non-root - Then stdout matches regexp: - """ - Client to manage Ubuntu Pro services on a machine. - - anbox-cloud: .* - - cc-eal: Common Criteria EAL2 Provisioning Packages - \(https://ubuntu.com/security/cc\) - - esm-apps: Expanded Security Maintenance for Applications - \(https://ubuntu.com/security/esm\) - - esm-infra: Expanded Security Maintenance for Infrastructure - \(https://ubuntu.com/security/esm\) - - fips-preview: .* - .*\(https://ubuntu.com/security/fips\) - - fips-updates: FIPS compliant crypto packages with stable security updates - \(https://ubuntu.com/security/fips\) - - fips: NIST-certified FIPS crypto packages \(https://ubuntu.com/security/fips\) - - landscape: Management and administration tool for Ubuntu - \(https://ubuntu.com/landscape\) - - livepatch: Canonical Livepatch service - \(https://ubuntu.com/security/livepatch\) - - realtime-kernel: Ubuntu kernel with PREEMPT_RT patches integrated - \(https://ubuntu.com/realtime-kernel\) - - ros-updates: All Updates for the Robot Operating System - \(https://ubuntu.com/robotics/ros-esm\) - - ros: Security Updates for the Robot Operating System - \(https://ubuntu.com/robotics/ros-esm\) - - usg: Security compliance and audit tools - \(https://ubuntu.com/security/certifications/docs/usg\) - """ - - Examples: ubuntu release - | release | machine_type | - | focal | lxd-container | - | jammy | lxd-container | - - Scenario Outline: Run timer script on an attached machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `systemctl stop ua-timer.timer` with sudo - And I attach `contract_token` with sudo - Then I verify that running `pro config set update_messaging_timer=-2` `with sudo` exits `1` - And stderr matches regexp: - """ - Cannot set update_messaging_timer to -2: for interval must be a positive integer. - """ - When I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo - And I run `cat /var/lib/ubuntu-advantage/jobs-status.json` with sudo - Then stdout matches regexp: - """ - "update_messaging": - """ - When I run `pro config show` with sudo - Then stdout matches regexp: - """ - update_messaging_timer +21600 - """ - When I delete the file `/var/lib/ubuntu-advantage/jobs-status.json` - And I run `pro config set update_messaging_timer=0` with sudo - And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo - And I run `cat /var/lib/ubuntu-advantage/jobs-status.json` with sudo - Then stdout matches regexp: - """ - "update_messaging": null - """ - When I delete the file `/var/lib/ubuntu-advantage/jobs-status.json` - And I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { "metering_timer": 0 } - """ - And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo - And I run `cat /var/lib/ubuntu-advantage/jobs-status.json` with sudo - Then stdout matches regexp: - """ - "metering": null - """ - When I delete the file `/var/lib/ubuntu-advantage/jobs-status.json` - And I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { "metering_timer": "notanumber", "update_messaging_timer": -10 } - """ - And I run `systemctl start ua-timer.service` with sudo - Then I verify that running `sh -c 'journalctl -u ua-timer.service | grep "Invalid value for update_messaging interval found in config."'` `with sudo` exits `0` - And I verify that the timer interval for `update_messaging` is `21600` - And I verify that the timer interval for `metering` is `14400` - When I create the file `/var/lib/ubuntu-advantage/jobs-status.json` with the following: - """ - {"metering": {"last_run": "2022-11-29T19:15:52.434906+00:00", "next_run": "2022-11-29T23:15:52.434906+00:00"}, "update_messaging": {"last_run": "2022-11-29T19:15:52.434906+00:00", "next_run": "2022-11-30T01:15:52.434906+00:00"}, "update_status": {"last_run": "2022-11-29T19:15:52.434906+00:00", "next_run": "2022-11-30T01:15:52.434906+00:00"}} - """ - And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo - And I run `cat /var/lib/ubuntu-advantage/jobs-status.json` with sudo - Then stdout does not match regexp: - """ - "update_status" - """ - And stdout matches regexp: - """ - "metering" - """ - And stdout matches regexp: - """ - "update_messaging" - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - Scenario Outline: Run timer script to valid machine activity endpoint - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I apt update - And I apt install `jq` - And I save the `activityInfo.activityToken` value from the contract - And I save the `activityInfo.activityID` value from the contract - # normal metering call when activityId is set by attach response above, expect new - # token and same id - And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo - Then I verify that `activityInfo.activityToken` value has been updated on the contract - And I verify that `activityInfo.activityID` value has not been updated on the contract - When I restore the saved `activityInfo.activityToken` value on contract - And I delete the file `/var/lib/ubuntu-advantage/jobs-status.json` - # simulate "cloned" metering call where previously used activityToken is sent again, - # expect new token and new id - And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo - Then I verify that `activityInfo.activityToken` value has been updated on the contract - And I verify that `activityInfo.activityID` value has been updated on the contract - # We are keeping this test to guarantee that the activityPingInterval is also updated - When I create the file `/var/lib/ubuntu-advantage/machine-token-overlay.json` with the following: - """ - { - "machineTokenInfo": { - "contractInfo": { - "id": "testCID" - }, - "machineId": "testMID" - } - } - """ - And I create the file `/var/lib/ubuntu-advantage/response-overlay.json` with the following: - """ - { - "https://contracts.canonical.com/v1/contracts/testCID/machine-activity/testMID": [ - { - "code": 200, - "response": { - "activityToken": "test-activity-token", - "activityID": "test-activity-id", - "activityPingInterval": 123456789 + Scenario Outline: Attached refresh in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I verify that `Bearer ` field is redacted in the logs + And I verify that `'attach', '` field is redacted in the logs + And I verify that `'machineToken': '` field is redacted in the logs + Then I verify that running `pro refresh` `as non-root` exits `1` + And stderr matches regexp: + """ + This command must be run as root \(try using sudo\). + """ + When I run `pro refresh` with sudo + Then I will see the following on stdout: + """ + Successfully processed your pro configuration. + Successfully refreshed your subscription. + Successfully updated Ubuntu Pro related APT and MOTD messages. + """ + When I run `pro refresh config` with sudo + Then I will see the following on stdout: + """ + Successfully processed your pro configuration. + """ + When I run `pro refresh contract` with sudo + Then I will see the following on stdout: + """ + Successfully refreshed your subscription. + """ + When I run `pro refresh messages` with sudo + Then I will see the following on stdout: + """ + Successfully updated Ubuntu Pro related APT and MOTD messages. + """ + + Examples: ubuntu release + | release | machine_type | + | bionic | lxd-container | + | bionic | wsl | + | focal | lxd-container | + | focal | wsl | + | xenial | lxd-container | + | jammy | lxd-container | + | jammy | wsl | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: Disable command on an attached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that running `pro disable livepatch` `as non-root` exits `1` + And stderr matches regexp: + """ + This command must be run as root \(try using sudo\). + """ + When I verify that running `pro disable foobar` `as non-root` exits `1` + Then stderr matches regexp: + """ + This command must be run as root \(try using sudo\). + """ + When I verify that running `pro disable livepatch` `with sudo` exits `1` + Then I will see the following on stdout: + """ + Livepatch is not currently enabled - nothing to do. + See: sudo pro status + """ + When I verify that running `pro disable foobar` `with sudo` exits `1` + Then stdout matches regexp: + """ + Cannot disable unknown service 'foobar'. + + """ + When I verify that running `pro disable livepatch foobar` `as non-root` exits `1` + Then stderr matches regexp: + """ + This command must be run as root \(try using sudo\) + """ + When I verify that running `pro disable livepatch foobar` `with sudo` exits `1` + Then stdout matches regexp: + """ + Livepatch is not currently enabled - nothing to do. + See: sudo pro status + Cannot disable unknown service 'foobar'. + + """ + When I verify that running `pro disable esm-infra` `as non-root` exits `1` + Then stderr matches regexp: + """ + This command must be run as root \(try using sudo\). + """ + When I run `pro disable esm-infra` with sudo + Then I verify that `esm-infra` is disabled + And I verify that running `apt update` `with sudo` exits `0` + + Examples: ubuntu release + | release | machine_type | msg | + | xenial | lxd-container | Try anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | + | bionic | lxd-container | Try anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | + | focal | lxd-container | Try anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | + | jammy | lxd-container | Try anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | + | noble | lxd-container | Try anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | + + Scenario Outline: Attached disable with json format + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that running `pro disable foobar --format json` `as non-root` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + Then I verify that running `pro disable foobar --format json` `with sudo` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + Then I verify that running `pro disable foobar --format json --assume-yes` `as non-root` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "This command must be run as root (try using sudo).", "message_code": "nonroot-user", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + And I verify that running `pro disable foobar --format json --assume-yes` `with sudo` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"additional_info": {"invalid_service": "foobar", "operation": "disable", "service_msg": "Try "}, "message": "Cannot disable unknown service 'foobar'.\nTry ", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + And I verify that running `pro disable livepatch --format json --assume-yes` `with sudo` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "Livepatch is not currently enabled - nothing to do.\nSee: sudo pro status", "message_code": "service-already-disabled", "service": "livepatch", "type": "service"}], "failed_services": ["livepatch"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + And I verify that running `pro disable esm-infra esm-apps --format json --assume-yes` `with sudo` exits `0` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-apps", "esm-infra"], "result": "success", "warnings": []} + """ + When I run `pro enable esm-infra` with sudo + Then I verify that running `pro disable esm-infra foobar --format json --assume-yes` `with sudo` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"additional_info": {"invalid_service": "foobar", "operation": "disable", "service_msg": "Try "}, "message": "Cannot disable unknown service 'foobar'.\nTry ", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-infra"], "result": "failure", "warnings": []} + """ + + Examples: ubuntu release + | release | machine_type | valid_services | + | xenial | lxd-container | anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | + | bionic | lxd-container | anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | + | focal | lxd-container | anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | + | jammy | lxd-container | anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | + | noble | lxd-container | anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | + + Scenario Outline: Attached detach in an ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I verify that `Bearer ` field is redacted in the logs + And I verify that `'attach', '` field is redacted in the logs + And I verify that `'machineToken': '` field is redacted in the logs + And I run `pro api u.pro.status.enabled_services.v1` as non-root + Then stdout matches regexp: + """ + {"_schema_version": "v1", "data": {"attributes": {"enabled_services": \[{"name": "esm-apps", "variant_enabled": false, "variant_name": null}, {"name": "esm-infra", "variant_enabled": false, "variant_name": null}\]}, "meta": {"environment_vars": \[\]}, "type": "EnabledServices"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} + """ + Then I verify that running `pro detach` `as non-root` exits `1` + And stderr matches regexp: + """ + This command must be run as root \(try using sudo\). + """ + When I run `pro detach --assume-yes` with sudo + Then I will see the following on stdout: + """ + Detach will disable the following services: + esm-apps + esm-infra + Removing APT access to Ubuntu Pro: ESM Apps + Updating package lists + Removing APT access to Ubuntu Pro: ESM Infra + Updating package lists + This machine is now detached. + """ + And the machine is unattached + And I ensure apt update runs without errors + When I attach `contract_token` with sudo + Then I verify that running `pro enable foobar --format json` `as non-root` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + Then I verify that running `pro enable foobar --format json` `with sudo` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + Then I verify that running `pro detach --format json --assume-yes` `as non-root` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "This command must be run as root (try using sudo).", "message_code": "nonroot-user", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + When I run `pro detach --format json --assume-yes` with sudo + Then stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-apps", "esm-infra"], "result": "success", "warnings": []} + """ + And the machine is unattached + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | bionic | wsl | + | focal | lxd-container | + | focal | wsl | + | jammy | lxd-container | + | jammy | wsl | + | noble | lxd-container | + + Scenario Outline: Attached auto-attach in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that running `pro auto-attach` `as non-root` exits `1` + And stderr matches regexp: + """ + This command must be run as root \(try using sudo\). + """ + When I verify that running `pro auto-attach` `with sudo` exits `2` + Then stderr matches regexp: + """ + This machine is already attached to '.+' + To use a different subscription first run: sudo pro detach. + """ + + Examples: ubuntu release + | release | machine_type | + | bionic | lxd-container | + | focal | lxd-container | + | xenial | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: Attached show version in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `pro version` as non-root + Then I will see the uaclient version on stdout + When I run `pro version` with sudo + Then I will see the uaclient version on stdout + When I run `pro --version` as non-root + Then I will see the uaclient version on stdout + When I run `pro --version` with sudo + Then I will see the uaclient version on stdout + + Examples: ubuntu release + | release | machine_type | + | bionic | lxd-container | + | focal | lxd-container | + | xenial | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: Attached status in a ubuntu machine with feature overrides + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/var/lib/ubuntu-advantage/machine-token-overlay.json` with the following: + """ + { + "machineTokenInfo": { + "contractInfo": { + "resourceEntitlements": [ + { + "type": "cc-eal", + "entitled": false + } + ] } - }] - } - """ - And I append the following on uaclient config: - """ - features: - machine_token_overlay: "/var/lib/ubuntu-advantage/machine-token-overlay.json" - serviceclient_url_responses: "/var/lib/ubuntu-advantage/response-overlay.json" - """ - When I delete the file `/var/lib/ubuntu-advantage/jobs-status.json` - And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo - Then I verify that running `grep -q activityInfo /var/lib/ubuntu-advantage/private/machine-token.json` `with sudo` exits `0` - And I verify that running `grep -q "\"activityToken\": \"test-activity-token\"" /var/lib/ubuntu-advantage/private/machine-token.json` `with sudo` exits `0` - And I verify that running `grep -q "\"activityID\": \"test-activity-id\"" /var/lib/ubuntu-advantage/private/machine-token.json` `with sudo` exits `0` - And I verify that running `grep -q "\"activityPingInterval\": 123456789" /var/lib/ubuntu-advantage/private/machine-token.json` `with sudo` exits `0` - When I run `cat /var/lib/ubuntu-advantage/jobs-status.json` with sudo - Then stdout matches regexp: - """ - \"metering\" - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - Scenario Outline: Run timer script to valid machine activity endpoint - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `rm /var/lib/ubuntu-advantage/machine-token.json` with sudo - Then the machine is unattached - When I run `dpkg-reconfigure ubuntu-advantage-tools` with sudo - Then I verify that files exist matching `/var/lib/ubuntu-advantage/machine-token.json` - Then the machine is attached - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - Scenario Outline: Disable with purge does not work with assume-yes - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I verify that running `pro disable esm-apps --assume-yes --purge` `with sudo` exits `1` - Then stderr contains substring: - """ - Error: Cannot use --purge together with --assume-yes. - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - Scenario Outline: Disable with purge works and purges repo services not involving a kernel - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I apt update - And I apt install `ansible` - And I run `pro disable esm-apps --purge` `with sudo` and stdin `y` - Then stdout matches regexp: - """ - \(The --purge flag is still experimental - use with caution\) - - The following package\(s\) will be reinstalled from the archive: - .*ansible.* - - Do you want to proceed\? \(y/N\) - """ - And I verify that `esm-apps` is disabled - And I verify that `ansible` is installed from apt source `http://archive.ubuntu.com/ubuntu /universe` - - Examples: ubuntu release - | release | machine_type | pocket | - # This ends up in GH #943 but maybe can be improved? - | xenial | lxd-container | xenial-backports | - | bionic | lxd-container | bionic-updates | - | focal | lxd-container | focal | - | jammy | lxd-container | jammy | - - Scenario Outline: Disable with purge unsupported services - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I verify that running `pro disable livepatch --purge` `with sudo` exits `1` - Then I will see the following on stdout: - """ - Livepatch does not support being disabled with --purge - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-vm | - | bionic | lxd-vm | - | focal | lxd-vm | - | jammy | lxd-vm | - - @slow - Scenario Outline: Disable and purge fips - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I apt update - And I run `pro enable --assume-yes` with sudo - And I reboot the machine - Then I verify that `` is enabled - When I run `uname -r` as non-root - Then stdout matches regexp: - """ - fips - """ - And I verify that `openssh-server` is installed from apt source `` - And I verify that `` is installed from apt source `` - When I run `pro disable --purge` `with sudo` and stdin `y\ny` - Then stdout matches regexp: - """ - \(The --purge flag is still experimental - use with caution\) - - Purging the packages would uninstall the following kernel\(s\): - .* - .* is the current running kernel\. - If you cannot guarantee that other kernels in this system are bootable and - working properly, \*do not proceed\*\. You may end up with an unbootable system\. - Do you want to proceed\? \(y/N\) - """ - And stdout matches regexp: - """ - The following package\(s\) will be REMOVED: - (.|\n)+ - - The following package\(s\) will be reinstalled from the archive: - (.|\n)+ - - Do you want to proceed\? \(y/N\) - """ - When I reboot the machine - Then I verify that `` is disabled - When I run `uname -r` as non-root - Then stdout does not match regexp: - """ - fips - """ - And I verify that `openssh-server` is installed from apt source `` - And I verify that `` is not installed - - Examples: ubuntu release - | release | machine_type | fips-service | fips-name | kernel-package | fips-source | archive-source | - | xenial | lxd-vm | fips | FIPS | linux-fips | https://esm.ubuntu.com/fips/ubuntu xenial/main | https://esm.ubuntu.com/infra/ubuntu xenial-infra-security/main | - | xenial | lxd-vm | fips-updates | FIPS Updates | linux-fips | https://esm.ubuntu.com/fips-updates/ubuntu xenial-updates/main | https://esm.ubuntu.com/infra/ubuntu xenial-infra-security/main | - | bionic | lxd-vm | fips | FIPS | linux-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | - | bionic | lxd-vm | fips-updates | FIPS Updates | linux-fips | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | - | bionic | aws.generic | fips | FIPS | linux-aws-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | - | bionic | aws.generic | fips-updates | FIPS Updates | linux-aws-fips | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | - | bionic | azure.generic | fips | FIPS | linux-azure-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | - | bionic | azure.generic | fips-updates | FIPS Updates | linux-azure-fips | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | - | bionic | gcp.generic | fips | FIPS | linux-gcp-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | - | bionic | gcp.generic | fips-updates | FIPS Updates | linux-gcp-fips | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | - | focal | lxd-vm | fips | FIPS | linux-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | http://archive.ubuntu.com/ubuntu focal-updates/main | - | focal | lxd-vm | fips-updates | FIPS Updates | linux-fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | http://archive.ubuntu.com/ubuntu focal-updates/main | - | focal | aws.generic | fips | FIPS | linux-aws-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | http://us-east-2.ec2.archive.ubuntu.com/ubuntu focal-updates/main | - | focal | aws.generic | fips-updates | FIPS Updates | linux-aws-fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | http://us-east-2.ec2.archive.ubuntu.com/ubuntu focal-updates/main | - | focal | azure.generic | fips | FIPS | linux-azure-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | http://azure.archive.ubuntu.com/ubuntu focal-updates/main | - | focal | azure.generic | fips-updates | FIPS Updates | linux-azure-fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | http://azure.archive.ubuntu.com/ubuntu focal-updates/main | - | focal | gcp.generic | fips | FIPS | linux-gcp-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | http://us-west2.gce.archive.ubuntu.com/ubuntu focal-updates/main | - | focal | gcp.generic | fips-updates | FIPS Updates | linux-gcp-fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | http://us-west2.gce.archive.ubuntu.com/ubuntu focal-updates/main | - - @slow - Scenario Outline: Disable does not purge if no other kernel found - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I apt update - And I run `pro enable fips --assume-yes` with sudo - And I reboot the machine - And I run shell command `rm -rf $(find /boot -name 'vmlinuz*[^fips]')` with sudo - And I verify that running `pro disable fips --purge` `with sudo` exits `1` - Then stdout matches regexp: - """ - \(The --purge flag is still experimental - use with caution\) - - Purging the FIPS packages would uninstall the following kernel\(s\): - .* - .* is the current running kernel\. - No other valid Ubuntu kernel was found in the system\. - Removing the package would potentially make the system unbootable\. - Aborting\. - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-vm | - | bionic | lxd-vm | - | focal | lxd-vm | + } + } + """ + And I append the following on uaclient config: + """ + features: + machine_token_overlay: "/var/lib/ubuntu-advantage/machine-token-overlay.json" + disable_auto_attach: true + other: false + """ + And I attach `contract_token` with sudo + And I run `pro status --all` with sudo + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +.* + cc-eal +no + """ + And stdout matches regexp: + """ + FEATURES + disable_auto_attach: True + machine_token_overlay: /var/lib/ubuntu-advantage/machine-token-overlay.json + other: False + """ + When I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +.* + cc-eal +no + """ + And stdout matches regexp: + """ + FEATURES + disable_auto_attach: True + machine_token_overlay: /var/lib/ubuntu-advantage/machine-token-overlay.json + other: False + """ + When I run `pro detach --assume-yes` with sudo + Then I verify that running `pro auto-attach` `with sudo` exits `1` + Then stderr matches regexp: + """ + features.disable_auto_attach set in config + """ + + Examples: ubuntu release + | release | machine_type | + | bionic | lxd-container | + | focal | lxd-container | + | xenial | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: Attached enable when reboot required + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `pro disable esm-infra` with sudo + And I run `touch /var/run/reboot-required` with sudo + And I run `touch /var/run/reboot-required.pkgs` with sudo + And I run `pro enable esm-infra` with sudo + Then stdout matches regexp: + """ + Updating Ubuntu Pro: ESM Infra package lists + Ubuntu Pro: ESM Infra enabled + """ + And stdout does not match regexp: + """ + A reboot is required to complete install. + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + Scenario Outline: Help command on an attached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `pro help esm-infra` with sudo + Then I will see the following on stdout: + """ + Name: + esm-infra + + Entitled: + yes + + Status: + + + Help: + Expanded Security Maintenance for Infrastructure provides access to a private + PPA which includes available high and critical CVE fixes for Ubuntu LTS + packages in the Ubuntu Main repository between the end of the standard Ubuntu + LTS security maintenance and its end of life. It is enabled by default with + Ubuntu Pro. You can find out more about the service at + https://ubuntu.com/security/esm + """ + When I run `pro help esm-infra --format json` with sudo + Then I will see the following on stdout: + """ + {"name": "esm-infra", "entitled": "yes", "status": "", "help": "Expanded Security Maintenance for Infrastructure provides access to a private\nPPA which includes available high and critical CVE fixes for Ubuntu LTS\npackages in the Ubuntu Main repository between the end of the standard Ubuntu\nLTS security maintenance and its end of life. It is enabled by default with\nUbuntu Pro. You can find out more about the service at\nhttps://ubuntu.com/security/esm"} + """ + And I verify that running `pro help invalid-service` `with sudo` exits `1` + And I will see the following on stderr: + """ + No help available for 'invalid-service' + """ + When I run `pro --help` as non-root + Then stdout matches regexp: + """ + Client to manage Ubuntu Pro services on a machine. + - anbox-cloud: .* + - cc-eal: Common Criteria EAL2 Provisioning Packages + \(https://ubuntu.com/security/cc\) + - cis: Security compliance and audit tools + \(https://ubuntu.com/security/certifications/docs/usg\) + - esm-apps: Expanded Security Maintenance for Applications + \(https://ubuntu.com/security/esm\) + - esm-infra: Expanded Security Maintenance for Infrastructure + \(https://ubuntu.com/security/esm\) + - fips-preview: .* + .*\(https://ubuntu.com/security/fips\) + - fips-updates: FIPS compliant crypto packages with stable security updates + \(https://ubuntu.com/security/fips\) + - fips: NIST-certified FIPS crypto packages \(https://ubuntu.com/security/fips\) + - landscape: Management and administration tool for Ubuntu + \(https://ubuntu.com/landscape\) + - livepatch: Canonical Livepatch service + \(https://ubuntu.com/security/livepatch\) + """ + When I run `pro help` with sudo + Then stdout matches regexp: + """ + Client to manage Ubuntu Pro services on a machine. + - anbox-cloud: .* + - cc-eal: Common Criteria EAL2 Provisioning Packages + \(https://ubuntu.com/security/cc\) + - cis: Security compliance and audit tools + \(https://ubuntu.com/security/certifications/docs/usg\) + - esm-apps: Expanded Security Maintenance for Applications + \(https://ubuntu.com/security/esm\) + - esm-infra: Expanded Security Maintenance for Infrastructure + \(https://ubuntu.com/security/esm\) + - fips-preview: .* + .*\(https://ubuntu.com/security/fips\) + - fips-updates: FIPS compliant crypto packages with stable security updates + \(https://ubuntu.com/security/fips\) + - fips: NIST-certified FIPS crypto packages \(https://ubuntu.com/security/fips\) + - landscape: Management and administration tool for Ubuntu + \(https://ubuntu.com/landscape\) + - livepatch: Canonical Livepatch service + \(https://ubuntu.com/security/livepatch\) + """ + When I run `pro help --all` as non-root + Then stdout matches regexp: + """ + Client to manage Ubuntu Pro services on a machine. + - anbox-cloud: .* + - cc-eal: Common Criteria EAL2 Provisioning Packages + \(https://ubuntu.com/security/cc\) + - cis: Security compliance and audit tools + \(https://ubuntu.com/security/certifications/docs/usg\) + - esm-apps: Expanded Security Maintenance for Applications + \(https://ubuntu.com/security/esm\) + - esm-infra: Expanded Security Maintenance for Infrastructure + \(https://ubuntu.com/security/esm\) + - fips-preview: .* + .*\(https://ubuntu.com/security/fips\) + - fips-updates: FIPS compliant crypto packages with stable security updates + \(https://ubuntu.com/security/fips\) + - fips: NIST-certified FIPS crypto packages \(https://ubuntu.com/security/fips\) + - landscape: Management and administration tool for Ubuntu + \(https://ubuntu.com/landscape\) + - livepatch: Canonical Livepatch service + \(https://ubuntu.com/security/livepatch\) + - realtime-kernel: Ubuntu kernel with PREEMPT_RT patches integrated + \(https://ubuntu.com/realtime-kernel\) + - ros-updates: All Updates for the Robot Operating System + \(https://ubuntu.com/robotics/ros-esm\) + - ros: Security Updates for the Robot Operating System + \(https://ubuntu.com/robotics/ros-esm\) + """ + + Examples: ubuntu release + | release | machine_type | infra-status | + | bionic | lxd-container | enabled | + | xenial | lxd-container | enabled | + | mantic | lxd-container | n/a | + + Scenario Outline: Help command on an attached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `pro help esm-infra` with sudo + Then I will see the following on stdout: + """ + Name: + esm-infra + + Entitled: + yes + + Status: + enabled + + Help: + Expanded Security Maintenance for Infrastructure provides access to a private + PPA which includes available high and critical CVE fixes for Ubuntu LTS + packages in the Ubuntu Main repository between the end of the standard Ubuntu + LTS security maintenance and its end of life. It is enabled by default with + Ubuntu Pro. You can find out more about the service at + https://ubuntu.com/security/esm + """ + When I run `pro help esm-infra --format json` with sudo + Then I will see the following on stdout: + """ + {"name": "esm-infra", "entitled": "yes", "status": "enabled", "help": "Expanded Security Maintenance for Infrastructure provides access to a private\nPPA which includes available high and critical CVE fixes for Ubuntu LTS\npackages in the Ubuntu Main repository between the end of the standard Ubuntu\nLTS security maintenance and its end of life. It is enabled by default with\nUbuntu Pro. You can find out more about the service at\nhttps://ubuntu.com/security/esm"} + """ + And I verify that running `pro help invalid-service` `with sudo` exits `1` + And I will see the following on stderr: + """ + No help available for 'invalid-service' + """ + When I run `pro --help` as non-root + Then stdout matches regexp: + """ + Client to manage Ubuntu Pro services on a machine. + - anbox-cloud: .* + - cc-eal: Common Criteria EAL2 Provisioning Packages + \(https://ubuntu.com/security/cc\) + - esm-apps: Expanded Security Maintenance for Applications + \(https://ubuntu.com/security/esm\) + - esm-infra: Expanded Security Maintenance for Infrastructure + \(https://ubuntu.com/security/esm\) + - fips-preview: .* + .*\(https://ubuntu.com/security/fips\) + - fips-updates: FIPS compliant crypto packages with stable security updates + \(https://ubuntu.com/security/fips\) + - fips: NIST-certified FIPS crypto packages \(https://ubuntu.com/security/fips\) + - landscape: Management and administration tool for Ubuntu + \(https://ubuntu.com/landscape\) + - livepatch: Canonical Livepatch service + \(https://ubuntu.com/security/livepatch\) + - realtime-kernel: Ubuntu kernel with PREEMPT_RT patches integrated + \(https://ubuntu.com/realtime-kernel\) + - ros-updates: All Updates for the Robot Operating System + \(https://ubuntu.com/robotics/ros-esm\) + - ros: Security Updates for the Robot Operating System + \(https://ubuntu.com/robotics/ros-esm\) + - usg: Security compliance and audit tools + \(https://ubuntu.com/security/certifications/docs/usg\) + """ + When I run `pro help` with sudo + Then stdout matches regexp: + """ + Client to manage Ubuntu Pro services on a machine. + - anbox-cloud: .* + - cc-eal: Common Criteria EAL2 Provisioning Packages + \(https://ubuntu.com/security/cc\) + - esm-apps: Expanded Security Maintenance for Applications + \(https://ubuntu.com/security/esm\) + - esm-infra: Expanded Security Maintenance for Infrastructure + \(https://ubuntu.com/security/esm\) + - fips-preview: .* + .*\(https://ubuntu.com/security/fips\) + - fips-updates: FIPS compliant crypto packages with stable security updates + \(https://ubuntu.com/security/fips\) + - fips: NIST-certified FIPS crypto packages \(https://ubuntu.com/security/fips\) + - landscape: Management and administration tool for Ubuntu + \(https://ubuntu.com/landscape\) + - livepatch: Canonical Livepatch service + \(https://ubuntu.com/security/livepatch\) + - realtime-kernel: Ubuntu kernel with PREEMPT_RT patches integrated + \(https://ubuntu.com/realtime-kernel\) + - ros-updates: All Updates for the Robot Operating System + \(https://ubuntu.com/robotics/ros-esm\) + - ros: Security Updates for the Robot Operating System + \(https://ubuntu.com/robotics/ros-esm\) + - usg: Security compliance and audit tools + \(https://ubuntu.com/security/certifications/docs/usg\) + """ + When I run `pro help --all` as non-root + Then stdout matches regexp: + """ + Client to manage Ubuntu Pro services on a machine. + - anbox-cloud: .* + - cc-eal: Common Criteria EAL2 Provisioning Packages + \(https://ubuntu.com/security/cc\) + - esm-apps: Expanded Security Maintenance for Applications + \(https://ubuntu.com/security/esm\) + - esm-infra: Expanded Security Maintenance for Infrastructure + \(https://ubuntu.com/security/esm\) + - fips-preview: .* + .*\(https://ubuntu.com/security/fips\) + - fips-updates: FIPS compliant crypto packages with stable security updates + \(https://ubuntu.com/security/fips\) + - fips: NIST-certified FIPS crypto packages \(https://ubuntu.com/security/fips\) + - landscape: Management and administration tool for Ubuntu + \(https://ubuntu.com/landscape\) + - livepatch: Canonical Livepatch service + \(https://ubuntu.com/security/livepatch\) + - realtime-kernel: Ubuntu kernel with PREEMPT_RT patches integrated + \(https://ubuntu.com/realtime-kernel\) + - ros-updates: All Updates for the Robot Operating System + \(https://ubuntu.com/robotics/ros-esm\) + - ros: Security Updates for the Robot Operating System + \(https://ubuntu.com/robotics/ros-esm\) + - usg: Security compliance and audit tools + \(https://ubuntu.com/security/certifications/docs/usg\) + """ + + Examples: ubuntu release + | release | machine_type | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + Scenario Outline: Run timer script on an attached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `systemctl stop ua-timer.timer` with sudo + And I attach `contract_token` with sudo + Then I verify that running `pro config set update_messaging_timer=-2` `with sudo` exits `1` + And stderr matches regexp: + """ + Cannot set update_messaging_timer to -2: for interval must be a positive integer. + """ + When I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo + And I run `cat /var/lib/ubuntu-advantage/jobs-status.json` with sudo + Then stdout matches regexp: + """ + "update_messaging": + """ + When I run `pro config show` with sudo + Then stdout matches regexp: + """ + update_messaging_timer +21600 + """ + When I delete the file `/var/lib/ubuntu-advantage/jobs-status.json` + And I run `pro config set update_messaging_timer=0` with sudo + And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo + And I run `cat /var/lib/ubuntu-advantage/jobs-status.json` with sudo + Then stdout matches regexp: + """ + "update_messaging": null + """ + When I delete the file `/var/lib/ubuntu-advantage/jobs-status.json` + And I create the file `/var/lib/ubuntu-advantage/private/user-config.json` with the following: + """ + { "metering_timer": 0 } + """ + And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo + And I run `cat /var/lib/ubuntu-advantage/jobs-status.json` with sudo + Then stdout matches regexp: + """ + "metering": null + """ + When I delete the file `/var/lib/ubuntu-advantage/jobs-status.json` + And I create the file `/var/lib/ubuntu-advantage/private/user-config.json` with the following: + """ + { "metering_timer": "notanumber", "update_messaging_timer": -10 } + """ + And I run `systemctl start ua-timer.service` with sudo + Then I verify that running `sh -c 'journalctl -u ua-timer.service | grep "Invalid value for update_messaging interval found in config."'` `with sudo` exits `0` + And I verify that the timer interval for `update_messaging` is `21600` + And I verify that the timer interval for `metering` is `14400` + When I create the file `/var/lib/ubuntu-advantage/jobs-status.json` with the following: + """ + {"metering": {"last_run": "2022-11-29T19:15:52.434906+00:00", "next_run": "2022-11-29T23:15:52.434906+00:00"}, "update_messaging": {"last_run": "2022-11-29T19:15:52.434906+00:00", "next_run": "2022-11-30T01:15:52.434906+00:00"}, "update_status": {"last_run": "2022-11-29T19:15:52.434906+00:00", "next_run": "2022-11-30T01:15:52.434906+00:00"}} + """ + And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo + And I run `cat /var/lib/ubuntu-advantage/jobs-status.json` with sudo + Then stdout does not match regexp: + """ + "update_status" + """ + And stdout matches regexp: + """ + "metering" + """ + And stdout matches regexp: + """ + "update_messaging" + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | bionic | wsl | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: Run timer script to valid machine activity endpoint + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I apt update + And I apt install `jq` + And I save the `activityInfo.activityToken` value from the contract + And I save the `activityInfo.activityID` value from the contract + # normal metering call when activityId is set by attach response above, expect new + # token and same id + And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo + Then I verify that `activityInfo.activityToken` value has been updated on the contract + And I verify that `activityInfo.activityID` value has not been updated on the contract + When I restore the saved `activityInfo.activityToken` value on contract + And I delete the file `/var/lib/ubuntu-advantage/jobs-status.json` + # simulate "cloned" metering call where previously used activityToken is sent again, + # expect new token and new id + And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo + Then I verify that `activityInfo.activityToken` value has been updated on the contract + And I verify that `activityInfo.activityID` value has been updated on the contract + # We are keeping this test to guarantee that the activityPingInterval is also updated + When I create the file `/var/lib/ubuntu-advantage/machine-token-overlay.json` with the following: + """ + { + "machineTokenInfo": { + "contractInfo": { + "id": "testCID" + }, + "machineId": "testMID" + } + } + """ + And I create the file `/var/lib/ubuntu-advantage/response-overlay.json` with the following: + """ + { + "https://contracts.canonical.com/v1/contracts/testCID/machine-activity/testMID": [ + { + "code": 200, + "response": { + "activityToken": "test-activity-token", + "activityID": "test-activity-id", + "activityPingInterval": 123456789 + } + }] + } + """ + And I append the following on uaclient config: + """ + features: + machine_token_overlay: "/var/lib/ubuntu-advantage/machine-token-overlay.json" + serviceclient_url_responses: "/var/lib/ubuntu-advantage/response-overlay.json" + """ + When I delete the file `/var/lib/ubuntu-advantage/jobs-status.json` + And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo + Then I verify that running `grep -q activityInfo /var/lib/ubuntu-advantage/private/machine-token.json` `with sudo` exits `0` + And I verify that running `grep -q "\"activityToken\": \"test-activity-token\"" /var/lib/ubuntu-advantage/private/machine-token.json` `with sudo` exits `0` + And I verify that running `grep -q "\"activityID\": \"test-activity-id\"" /var/lib/ubuntu-advantage/private/machine-token.json` `with sudo` exits `0` + And I verify that running `grep -q "\"activityPingInterval\": 123456789" /var/lib/ubuntu-advantage/private/machine-token.json` `with sudo` exits `0` + When I run `cat /var/lib/ubuntu-advantage/jobs-status.json` with sudo + Then stdout matches regexp: + """ + \"metering\" + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + Scenario Outline: Run timer script to valid machine activity endpoint + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `rm /var/lib/ubuntu-advantage/machine-token.json` with sudo + Then the machine is unattached + When I run `dpkg-reconfigure ubuntu-advantage-tools` with sudo + Then I verify that files exist matching `/var/lib/ubuntu-advantage/machine-token.json` + Then the machine is attached + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + Scenario Outline: Disable with purge does not work with assume-yes + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I verify that running `pro disable esm-apps --assume-yes --purge` `with sudo` exits `1` + Then stderr contains substring: + """ + Error: Cannot use --purge together with --assume-yes. + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + Scenario Outline: Disable with purge works and purges repo services not involving a kernel + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I apt update + And I apt install `ansible` + And I run `pro disable esm-apps --purge` `with sudo` and stdin `y` + Then stdout matches regexp: + """ + \(The --purge flag is still experimental - use with caution\) + + The following package\(s\) will be reinstalled from the archive: + .*ansible.* + + Do you want to proceed\? \(y/N\) + """ + And I verify that `esm-apps` is disabled + And I verify that `ansible` is installed from apt source `http://archive.ubuntu.com/ubuntu /universe` + + Examples: ubuntu release + | release | machine_type | pocket | + # This ends up in GH #943 but maybe can be improved? + | xenial | lxd-container | xenial-backports | + | bionic | lxd-container | bionic-updates | + | bionic | wsl | bionic-updates | + | focal | lxd-container | focal | + | jammy | lxd-container | jammy | + + Scenario Outline: Disable with purge unsupported services + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I verify that running `pro disable livepatch --purge` `with sudo` exits `1` + Then I will see the following on stdout: + """ + Livepatch does not support being disabled with --purge + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-vm | + | bionic | lxd-vm | + | focal | lxd-vm | + | jammy | lxd-vm | + | noble | lxd-vm | + + @slow + Scenario Outline: Disable and purge fips + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I apt update + And I run `pro enable --assume-yes` with sudo + And I reboot the machine + Then I verify that `` is enabled + When I run `uname -r` as non-root + Then stdout matches regexp: + """ + fips + """ + And I verify that `openssh-server` is installed from apt source `` + And I verify that `` is installed from apt source `` + When I run `pro disable --purge` `with sudo` and stdin `y\ny` + Then stdout matches regexp: + """ + \(The --purge flag is still experimental - use with caution\) + + Purging the packages would uninstall the following kernel\(s\): + .* + .* is the current running kernel\. + If you cannot guarantee that other kernels in this system are bootable and + working properly, \*do not proceed\*\. You may end up with an unbootable system\. + Do you want to proceed\? \(y/N\) + """ + And stdout matches regexp: + """ + The following package\(s\) will be REMOVED: + (.|\n)+ + + The following package\(s\) will be reinstalled from the archive: + (.|\n)+ + + Do you want to proceed\? \(y/N\) + """ + When I reboot the machine + Then I verify that `` is disabled + When I run `uname -r` as non-root + Then stdout does not match regexp: + """ + fips + """ + And I verify that `openssh-server` is installed from apt source `` + And I verify that `` is not installed + + Examples: ubuntu release + | release | machine_type | fips-service | fips-name | kernel-package | fips-source | archive-source | + | xenial | lxd-vm | fips | FIPS | linux-fips | https://esm.ubuntu.com/fips/ubuntu xenial/main | https://esm.ubuntu.com/infra/ubuntu xenial-infra-security/main | + | xenial | lxd-vm | fips-updates | FIPS Updates | linux-fips | https://esm.ubuntu.com/fips-updates/ubuntu xenial-updates/main | https://esm.ubuntu.com/infra/ubuntu xenial-infra-security/main | + | bionic | lxd-vm | fips | FIPS | linux-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | + | bionic | lxd-vm | fips-updates | FIPS Updates | linux-fips | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | + | bionic | aws.generic | fips | FIPS | linux-aws-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | + | bionic | aws.generic | fips-updates | FIPS Updates | linux-aws-fips | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | + | bionic | azure.generic | fips | FIPS | linux-azure-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | + | bionic | azure.generic | fips-updates | FIPS Updates | linux-azure-fips | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | + | bionic | gcp.generic | fips | FIPS | linux-gcp-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | + | bionic | gcp.generic | fips-updates | FIPS Updates | linux-gcp-fips | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | https://esm.ubuntu.com/infra/ubuntu bionic-infra-security/main | + | focal | lxd-vm | fips | FIPS | linux-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | http://archive.ubuntu.com/ubuntu focal-updates/main | + | focal | lxd-vm | fips-updates | FIPS Updates | linux-fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | http://archive.ubuntu.com/ubuntu focal-updates/main | + | focal | aws.generic | fips | FIPS | linux-aws-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | http://us-east-2.ec2.archive.ubuntu.com/ubuntu focal-updates/main | + | focal | aws.generic | fips-updates | FIPS Updates | linux-aws-fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | http://us-east-2.ec2.archive.ubuntu.com/ubuntu focal-updates/main | + | focal | azure.generic | fips | FIPS | linux-azure-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | http://azure.archive.ubuntu.com/ubuntu focal-updates/main | + | focal | azure.generic | fips-updates | FIPS Updates | linux-azure-fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | http://azure.archive.ubuntu.com/ubuntu focal-updates/main | + | focal | gcp.generic | fips | FIPS | linux-gcp-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | http://us-west2.gce.archive.ubuntu.com/ubuntu focal-updates/main | + | focal | gcp.generic | fips-updates | FIPS Updates | linux-gcp-fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | http://us-west2.gce.archive.ubuntu.com/ubuntu focal-updates/main | + + @slow + Scenario Outline: Disable does not purge if no other kernel found + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I apt update + And I run `pro enable fips --assume-yes` with sudo + And I reboot the machine + And I run shell command `rm -rf $(find /boot -name 'vmlinuz*[^fips]')` with sudo + And I verify that running `pro disable fips --purge` `with sudo` exits `1` + Then stdout matches regexp: + """ + \(The --purge flag is still experimental - use with caution\) + + Purging the FIPS packages would uninstall the following kernel\(s\): + .* + .* is the current running kernel\. + No other valid Ubuntu kernel was found in the system\. + Removing the package would potentially make the system unbootable\. + Aborting\. + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-vm | + | bionic | lxd-vm | + | focal | lxd-vm | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/attached_enable.feature ubuntu-advantage-tools-32~16.04/features/attached_enable.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/attached_enable.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/attached_enable.feature 2024-05-10 17:07:05.000000000 +0000 @@ -1,1032 +1,1049 @@ @uses.config.contract_token Feature: Enable command behaviour when attached to an Ubuntu Pro subscription - Scenario Outline: Attached enable Common Criteria service in an ubuntu lxd container - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that running `pro enable cc-eal` `as non-root` exits `1` - And I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - When I run `pro enable cc-eal` with sudo - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Updating CC EAL2 package lists - (This will download more than 500MB of packages, so may take some time.) - Updating standard Ubuntu package lists - Installing CC EAL2 packages - CC EAL2 enabled - Please follow instructions in /usr/share/doc/ubuntu-commoncriteria/README to configure EAL2 - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - - Scenario Outline: Enable cc-eal with --access-only - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - When I run `pro enable cc-eal --access-only` with sudo - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Updating CC EAL2 package lists - Skipping installing packages: ubuntu-commoncriteria - CC EAL2 access enabled - """ - Then I verify that running `apt-get install ubuntu-commoncriteria` `with sudo` exits `0` - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - - Scenario Outline: Attached enable Common Criteria service in an ubuntu lxd container - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that running `pro enable cc-eal` `as non-root` exits `1` - And I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - When I verify that running `pro enable cc-eal` `with sudo` exits `1` - Then I will see the following on stdout: - """ - One moment, checking your subscription first - CC EAL2 is not available for Ubuntu (). - """ - Examples: ubuntu release - | release | machine_type | version | full_name | - | focal | lxd-container | 20.04 LTS | Focal Fossa | - | jammy | lxd-container | 22.04 LTS | Jammy Jellyfish | - | mantic | lxd-container | 23.10 | Mantic Minotaur | - - Scenario Outline: Empty series affordance means no series, null means all series - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo and options `--no-auto-enable` - When I set the machine token overlay to the following yaml - """ - machineTokenInfo: - contractInfo: - resourceEntitlements: - - type: esm-infra - affordances: - series: [] - """ - When I verify that running `pro enable esm-infra` `with sudo` exits `1` - Then stdout matches regexp: - """ - One moment, checking your subscription first - Ubuntu Pro: ESM Infra is not available for Ubuntu .* - """ - When I create the file `/var/lib/ubuntu-advantage/machine-token-overlay.json` with the following: - """ - { - "machineTokenInfo": { - "contractInfo": { - "resourceEntitlements": [ - { - "type": "esm-infra", - "affordances": { - "series": null - } - } - ] - } - } - } - """ - When I verify that running `pro enable esm-infra` `with sudo` exits `0` - Then stdout matches regexp: - """ - One moment, checking your subscription first - Updating Ubuntu Pro: ESM Infra package lists - Ubuntu Pro: ESM Infra enabled - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - Scenario Outline: Attached enable of different services using json format - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that running `pro enable foobar --format json` `as non-root` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - Then I verify that running `pro enable foobar --format json` `with sudo` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - Then I verify that running `pro enable foobar --format json --assume-yes` `as non-root` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "This command must be run as root (try using sudo).", "message_code": "nonroot-user", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - And I verify that running `pro enable foobar --format json --assume-yes` `with sudo` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"additional_info": {"invalid_service": "foobar", "operation": "enable", "service_msg": "Try "}, "message": "Cannot enable unknown service 'foobar'.\nTry ", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": ["foobar"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - And I verify that running `pro enable blah foobar --format json --assume-yes` `with sudo` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"additional_info": {"invalid_service": "blah, foobar", "operation": "enable", "service_msg": "Try "}, "message": "Cannot enable unknown service 'blah, foobar'.\nTry ", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": ["blah", "foobar"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - And I verify that running `pro enable esm-infra --format json --assume-yes` `with sudo` exits `1` - And stdout is a json matching the `ua_operation` schema - Then I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "Ubuntu Pro: ESM Infra is already enabled.\nSee: sudo pro status", "message_code": "service-already-enabled", "service": "esm-infra", "type": "service"}], "failed_services": ["esm-infra"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - When I run `pro disable esm-infra` with sudo - And I run `pro enable esm-infra --format json --assume-yes` with sudo - Then stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-infra"], "result": "success", "warnings": []} - """ - When I run `pro disable esm-infra` with sudo - And I verify that running `pro enable esm-infra foobar --format json --assume-yes` `with sudo` exits `1` - Then stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"additional_info": {"invalid_service": "foobar", "operation": "enable", "service_msg": "Try "}, "message": "Cannot enable unknown service 'foobar'.\nTry ", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": ["foobar"], "needs_reboot": false, "processed_services": ["esm-infra"], "result": "failure", "warnings": []} - """ - When I run `pro disable esm-infra esm-apps` with sudo - And I run `pro enable esm-infra esm-apps --beta --format json --assume-yes` with sudo - Then stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-apps", "esm-infra"], "result": "success", "warnings": []} - """ - - Examples: ubuntu release - | release | machine_type | valid_services | - | xenial | lxd-container | anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | - | bionic | lxd-container | anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | - | focal | lxd-container | anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | - | jammy | lxd-container | anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | - - Scenario Outline: Attached enable of a service in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that running `pro enable foobar` `as non-root` exits `1` - And I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - And I verify that running `pro enable foobar` `with sudo` exits `1` - And I will see the following on stdout: - """ - One moment, checking your subscription first - """ - And stderr matches regexp: - """ - Cannot enable unknown service 'foobar'. - - """ - And I verify that running `pro enable blah foobar` `with sudo` exits `1` - And I will see the following on stdout: - """ - One moment, checking your subscription first - """ - And stderr matches regexp: - """ - Cannot enable unknown service 'blah, foobar'. - - """ - And I verify that running `pro enable esm-infra` `with sudo` exits `1` - And I will see the following on stdout: - """ - One moment, checking your subscription first - Ubuntu Pro: ESM Infra is already enabled. - See: sudo pro status - """ - When I run `apt-cache policy` with sudo - Then apt-cache policy for the following url has priority `510` - """ - -infra-updates/main amd64 Packages - """ - And apt-cache policy for the following url has priority `510` - """ - -infra-security/main amd64 Packages - """ - And I ensure apt update runs without errors - When I apt install `` - And I run `apt-cache policy ` as non-root - Then stdout matches regexp: - """ - \s*510 -infra-security/main amd64 Packages - """ - - Examples: ubuntu release - | release | machine_type | infra-pkg | esm-infra-url | msg | - | xenial | lxd-container | libkrad0 | https://esm.ubuntu.com/infra/ubuntu | Try anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | - | bionic | lxd-container | libkrad0 | https://esm.ubuntu.com/infra/ubuntu | Try anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | - | focal | lxd-container | hello | https://esm.ubuntu.com/infra/ubuntu | Try anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | - - Scenario Outline: Attached enable of non-container services in a ubuntu lxd container - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that running `pro enable livepatch` `as non-root` exits `1` - And I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - And I verify that running `pro enable livepatch` `with sudo` exits `1` - And I will see the following on stdout: - """ - One moment, checking your subscription first - Cannot install Livepatch on a container. - """ - - Examples: Un-supported services in containers - | release | machine_type | - | bionic | lxd-container | - | focal | lxd-container | - | xenial | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - Scenario Outline: Attached enable not entitled service in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I set the machine token overlay to the following yaml - """ - machineTokenInfo: - contractInfo: - resourceEntitlements: - - type: esm-apps - entitled: false - """ - When I attach `contract_token` with sudo - Then I verify that running `pro enable esm-apps` `as non-root` exits `1` - And I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - And I verify that running `pro enable esm-apps --beta` `with sudo` exits `1` - And I will see the following on stdout: - """ - One moment, checking your subscription first - This subscription is not entitled to Ubuntu Pro: ESM Apps - View your subscription at: https://ubuntu.com/pro/dashboard - """ - - Examples: not entitled services - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - Scenario Outline: Attached enable of cis service in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I verify that running `pro enable cis --access-only` `with sudo` exits `0` - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Updating CIS Audit package lists - Skipping installing packages: usg-cisbenchmark usg-common - CIS Audit access enabled - Visit https://ubuntu.com/security/cis to learn how to use CIS - """ - When I run `pro disable cis` with sudo - And I verify that running `pro enable cis` `with sudo` exits `0` - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Updating CIS Audit package lists - Updating standard Ubuntu package lists - Installing CIS Audit packages - CIS Audit enabled - Visit https://ubuntu.com/security/cis to learn how to use CIS - """ - When I run `apt-cache policy usg-cisbenchmark` as non-root - Then stdout does not match regexp: - """ - .*Installed: \(none\) - """ - And stdout matches regexp: - """ - \s* 500 https://esm.ubuntu.com/cis/ubuntu /main amd64 Packages - """ - When I run `apt-cache policy usg-common` as non-root - Then stdout does not match regexp: - """ - .*Installed: \(none\) - """ - And stdout matches regexp: - """ - \s* 500 https://esm.ubuntu.com/cis/ubuntu /main amd64 Packages - """ - When I verify that running `pro enable cis` `with sudo` exits `1` - Then stdout matches regexp - """ - One moment, checking your subscription first - CIS Audit is already enabled. - See: sudo pro status - """ - When I run `cis-audit level1_server` with sudo - Then stdout matches regexp - """ - Title.*Ensure no duplicate UIDs exist - Rule.*xccdf_com.ubuntu..cis_rule_CIS-.* - Result.*pass - """ - And stdout matches regexp: - """ - Title.*Ensure default user umask is 027 or more restrictive - Rule.*xccdf_com.ubuntu..cis_rule_CIS-.* - Result.*fail - """ - And stdout matches regexp - """ - CIS audit scan completed - """ - When I verify that running `/usr/share/ubuntu-scap-security-guides/cis-hardening/ lvl1_server` `with sudo` exits `0` - And I run `cis-audit level1_server` with sudo - Then stdout matches regexp: - """ - Title.*Ensure default user umask is 027 or more restrictive - Rule.*xccdf_com.ubuntu..cis_rule_CIS-.* - Result.*pass - """ - And stdout matches regexp - """ - CIS audit scan completed - """ - - Examples: cis script - | release | machine_type | cis_script | - | bionic | lxd-container | Canonical_Ubuntu_18.04_CIS-harden.sh | - | xenial | lxd-container | Canonical_Ubuntu_16.04_CIS_v1.1.0-harden.sh | - - Scenario Outline: Attached enable of cis service in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I verify that running `pro enable cis` `with sudo` exits `0` - Then I will see the following on stdout: - """ - One moment, checking your subscription first - From Ubuntu 20.04 onward 'pro enable cis' has been - replaced by 'pro enable usg'. See more information at: - https://ubuntu.com/security/certifications/docs/usg - Updating CIS Audit package lists - Updating standard Ubuntu package lists - Installing CIS Audit packages - CIS Audit enabled - Visit https://ubuntu.com/security/cis to learn how to use CIS - """ - When I run `apt-cache policy usg-cisbenchmark` as non-root - Then stdout does not match regexp: - """ - .*Installed: \(none\) - """ - And stdout matches regexp: - """ - \s* 500 https://esm.ubuntu.com/cis/ubuntu /main amd64 Packages - """ - When I run `apt-cache policy usg-common` as non-root - Then stdout does not match regexp: - """ - .*Installed: \(none\) - """ - And stdout matches regexp: - """ - \s* 500 https://esm.ubuntu.com/cis/ubuntu /main amd64 Packages - """ - When I verify that running `pro enable cis` `with sudo` exits `1` - Then stdout matches regexp - """ - One moment, checking your subscription first - From Ubuntu 20.04 onward 'pro enable cis' has been - replaced by 'pro enable usg'. See more information at: - https://ubuntu.com/security/certifications/docs/usg - CIS Audit is already enabled. - See: sudo pro status - """ - When I run `cis-audit level1_server` with sudo - Then stdout matches regexp - """ - Title.*Ensure no duplicate UIDs exist - Rule.*xccdf_com.ubuntu..cis_rule_CIS-.* - Result.*pass - """ - And stdout matches regexp: - """ - Title.*Ensure default user umask is 027 or more restrictive - Rule.*xccdf_com.ubuntu..cis_rule_CIS-.* - Result.*fail - """ - And stdout matches regexp - """ - CIS audit scan completed - """ - When I verify that running `/usr/share/ubuntu-scap-security-guides/cis-hardening/ lvl1_server` `with sudo` exits `0` - And I run `cis-audit level1_server` with sudo - Then stdout matches regexp: - """ - Title.*Ensure default user umask is 027 or more restrictive - Rule.*xccdf_com.ubuntu..cis_rule_CIS-.* - Result.*pass - """ - And stdout matches regexp - """ - CIS audit scan completed - """ - - Examples: cis script - | release | machine_type | cis_script | - | focal | lxd-container | Canonical_Ubuntu_20.04_CIS-harden.sh | - - Scenario Outline: Attached enable of usg service in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I verify that running `pro enable usg` `with sudo` exits `1` - Then I will see the following on stdout: - """ - One moment, checking your subscription first - """ - And stderr matches regexp: - """ - Cannot enable unknown service 'usg'. - Try anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates\. - """ - - Examples: cis service - | release | machine_type | - | bionic | lxd-container | - | xenial | lxd-container | - - Scenario Outline: Attached enable of usg service in a focal machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `pro enable usg` with sudo - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Updating Ubuntu Security Guide package lists - Ubuntu Security Guide enabled - Visit https://ubuntu.com/security/certifications/docs/usg for the next steps - """ - And I verify that `usg` is enabled - When I run `pro disable usg` with sudo - Then stdout matches regexp: - """ - Updating package lists - """ - And I verify that `usg` is disabled - When I run `pro enable cis` with sudo - Then I will see the following on stdout: - """ - One moment, checking your subscription first - From Ubuntu 20.04 onward 'pro enable cis' has been - replaced by 'pro enable usg'. See more information at: - https://ubuntu.com/security/certifications/docs/usg - Updating CIS Audit package lists - Updating standard Ubuntu package lists - Installing CIS Audit packages - CIS Audit enabled - Visit https://ubuntu.com/security/cis to learn how to use CIS - """ - And I verify that `usg` is enabled - When I run `pro disable usg` with sudo - Then stdout matches regexp: - """ - Updating package lists - """ - And I verify that `usg` is disabled - - Examples: cis service - | release | machine_type | - | focal | lxd-container | - - Scenario Outline: Attached disable of livepatch in a lxd vm - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that `esm-apps` is enabled - And I verify that `esm-infra` is enabled - And I verify that `livepatch` status is `` - When I run `pro disable livepatch` with sudo - Then I verify that running `canonical-livepatch status` `with sudo` exits `1` - And stderr matches regexp: - """ - Machine is not enabled. Please run 'sudo canonical-livepatch enable' with the - token obtained from https://ubuntu.com/livepatch. - """ - And I verify that `esm-apps` is enabled - And I verify that `esm-infra` is enabled - And I verify that `livepatch` is disabled - When I verify that running `pro enable livepatch --access-only` `with sudo` exits `1` - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Livepatch does not support being enabled with --access-only - """ - - Examples: ubuntu release - | release | machine_type | livepatch_status | - | xenial | lxd-vm | warning | - | bionic | lxd-vm | enabled | - - Scenario Outline: Attach works when snapd cannot be installed - Given a `` `` machine with ubuntu-advantage-tools installed - When I apt remove `snapd` - And I create the file `/etc/apt/preferences.d/no-snapd` with the following - """ - Package: snapd - Pin: release o=* - Pin-Priority: -10 - """ - And I apt update - When I attempt to attach `contract_token` with sudo - Then I will see the following on stderr: - """ - Enabling default service esm-apps - Enabling default service esm-infra - Enabling default service livepatch - Failed to enable default services, check: sudo pro status - """ - And I verify that `livepatch` is disabled - And I verify that running `pro enable livepatch` `with sudo` exits `1` - And I will see the following on stdout: - """ - One moment, checking your subscription first - Installing snapd - Updating standard Ubuntu package lists - Failed to install snapd on the system - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-vm | - | bionic | lxd-vm | - - Scenario Outline: Attached enable livepatch - Given a `` `` machine with ubuntu-advantage-tools installed - When I verify that running `canonical-livepatch status` `with sudo` exits `1` - Then I will see the following on stderr: - """ - sudo: canonical-livepatch: command not found - """ - When I attach `contract_token` with sudo - Then stdout matches regexp: - """ - Installing canonical-livepatch snap - Canonical Livepatch enabled - """ - And I verify that `livepatch` status is `` - When I run `canonical-livepatch status` with sudo - Then stdout matches regexp: - """ - running: true - """ - - Examples: ubuntu release - | release | machine_type | livepatch_status | - | xenial | lxd-vm | warning | - | bionic | lxd-vm | enabled | - - Scenario Outline: Attached enable livepatch - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then stdout matches regexp: - """ - Installing canonical-livepatch snap - Canonical Livepatch enabled - """ - And I verify that `livepatch` status is warning - When I run `pro api u.pro.security.status.reboot_required.v1` with sudo - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"livepatch_enabled": true, "livepatch_enabled_and_kernel_patched": true, "livepatch_state": "applied", "livepatch_support": "kernel-upgrade-required", "reboot_required": "no", "reboot_required_packages": {"kernel_packages": null, "standard_packages": null}}, "meta": {"environment_vars": \[\]}, "type": "RebootRequired"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro system reboot-required` as non-root - Then I will see the following on stdout: - """ - no - """ - When I apt install `libc6` - And I run `pro api u.pro.security.status.reboot_required.v1` as non-root - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"livepatch_enabled": true, "livepatch_enabled_and_kernel_patched": true, "livepatch_state": "applied", "livepatch_support": "kernel-upgrade-required", "reboot_required": "yes", "reboot_required_packages": {"kernel_packages": \[\], "standard_packages": \["libc6"\]}}, "meta": {"environment_vars": \[\]}, "type": "RebootRequired"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro system reboot-required` as non-root - Then I will see the following on stdout: - """ - yes - """ - When I reboot the machine - And I run `pro system reboot-required` as non-root - Then I will see the following on stdout: - """ - no - """ - When I apt install `linux-image-generic` - And I run `pro api u.pro.security.status.reboot_required.v1` as non-root - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"livepatch_enabled": true, "livepatch_enabled_and_kernel_patched": true, "livepatch_state": "applied", "livepatch_support": "kernel-upgrade-required", "reboot_required": "yes", "reboot_required_packages": {"kernel_packages": \["linux-base"\], "standard_packages": \[\]}}, "meta": {"environment_vars": \[\]}, "type": "RebootRequired"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro system reboot-required` as non-root - Then I will see the following on stdout: - """ - yes - """ - When I apt install `dbus` - And I run `pro api u.pro.security.status.reboot_required.v1` with sudo - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"livepatch_enabled": true, "livepatch_enabled_and_kernel_patched": true, "livepatch_state": "applied", "livepatch_support": "kernel-upgrade-required", "reboot_required": "yes", "reboot_required_packages": {"kernel_packages": \["linux-base"\], "standard_packages": \["dbus"\]}}, "meta": {"environment_vars": \[\]}, "type": "RebootRequired"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro system reboot-required` as non-root - Then I will see the following on stdout: - """ - yes - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-vm | - - @slow - Scenario: Attached enable livepatch on a machine with fips active - Given a `bionic` `lxd-vm` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then stdout matches regexp: - """ - Updating Ubuntu Pro: ESM Infra package lists - Ubuntu Pro: ESM Infra enabled - Installing snapd snap - Installing canonical-livepatch snap - Canonical Livepatch enabled - """ - When I run `pro disable livepatch` with sudo - And I run `pro enable fips --assume-yes` with sudo - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Updating FIPS package lists - Installing FIPS packages - Updating standard Ubuntu package lists - FIPS enabled - A reboot is required to complete install. - """ - When I append the following on uaclient config: - """ - features: - block_disable_on_enable: true - """ - Then I verify that running `pro enable livepatch` `with sudo` exits `1` - And I will see the following on stdout - """ - One moment, checking your subscription first - Cannot enable Livepatch when FIPS is enabled. - """ - Then I verify that running `pro enable livepatch --format json --assume-yes` `with sudo` exits `1` - And I will see the following on stdout - """ - {"_schema_version": "0.1", "errors": [{"message": "Cannot enable Livepatch when FIPS is enabled.", "message_code": "livepatch-error-when-fips-enabled", "service": "livepatch", "type": "service"}], "failed_services": ["livepatch"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - - Scenario: Attached enable fips on a machine with livepatch active - Given a `bionic` `lxd-vm` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then stdout matches regexp: - """ - Updating Ubuntu Pro: ESM Infra package lists - Ubuntu Pro: ESM Infra enabled - Installing snapd snap - Installing canonical-livepatch snap - Canonical Livepatch enabled - """ - When I append the following on uaclient config: - """ - features: - block_disable_on_enable: true - """ - Then I verify that running `pro enable fips --assume-yes` `with sudo` exits `1` - And I will see the following on stdout - """ - One moment, checking your subscription first - Cannot enable FIPS when Livepatch is enabled. - """ - Then I verify that running `pro enable fips --assume-yes --format json` `with sudo` exits `1` - And stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "Cannot enable FIPS when Livepatch is enabled.", "message_code": "incompatible-service-stops-enable", "service": "fips", "type": "service"}], "failed_services": ["fips"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - - @slow - Scenario Outline: Attached enable fips on a machine with livepatch active - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then stdout matches regexp: - """ - Updating Ubuntu Pro: ESM Infra package lists - Ubuntu Pro: ESM Infra enabled - """ - And stdout matches regexp: - """ - Installing canonical-livepatch snap - Canonical Livepatch enabled - """ - When I run `pro enable fips --assume-yes` with sudo - Then I will see the following on stdout - """ - One moment, checking your subscription first - Disabling incompatible service: Livepatch - Updating FIPS package lists - Installing FIPS packages - Updating standard Ubuntu package lists - FIPS enabled - A reboot is required to complete install. - """ - When I run `pro status --all` with sudo - Then stdout matches regexp: - """ - fips +yes +enabled - """ - And stdout matches regexp: - """ - livepatch +yes +n/a - """ - - Examples: ubuntu release - | release | machine_type | - | bionic | lxd-vm | - | xenial | lxd-vm | - - @slow - Scenario Outline: Attached enable fips on a machine with fips-updates active - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then stdout matches regexp: - """ - Ubuntu Pro: ESM Infra enabled - """ - And stdout matches regexp: - """ - Installing canonical-livepatch snap - Canonical Livepatch enabled - """ - When I run `pro disable livepatch` with sudo - And I run `pro enable fips-updates --assume-yes` with sudo - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Updating FIPS Updates package lists - Installing FIPS Updates packages - Updating standard Ubuntu package lists - FIPS Updates enabled - A reboot is required to complete install. - """ - When I verify that running `pro enable fips --assume-yes` `with sudo` exits `1` - Then I will see the following on stdout - """ - One moment, checking your subscription first - Cannot enable FIPS when FIPS Updates is enabled. - """ - - Examples: ubuntu release - | release | machine_type | - | bionic | lxd-vm | - | xenial | lxd-vm | - - Scenario Outline: Attached enable ros on a machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that `ros` is disabled - When I run `pro enable ros --assume-yes` with sudo - Then I verify that `ros` is enabled - And I verify that `esm-apps` is enabled - And I verify that `esm-infra` is enabled - When I verify that running `pro disable esm-apps` `with sudo` and stdin `N` exits `1` - Then stdout matches regexp - """ - ROS ESM Security Updates depends on Ubuntu Pro: ESM Apps. - Disable ROS ESM Security Updates and proceed to disable Ubuntu Pro: ESM Apps\? \(y\/N\) Cannot disable Ubuntu Pro: ESM Apps when ROS ESM Security Updates is enabled. - """ - When I run `pro disable esm-apps` `with sudo` and stdin `y` - Then stdout matches regexp - """ - ROS ESM Security Updates depends on Ubuntu Pro: ESM Apps. - Disable ROS ESM Security Updates and proceed to disable Ubuntu Pro: ESM Apps\? \(y\/N\) Disabling dependent service: ROS ESM Security Updates - Updating package lists - """ - And I verify that `ros` is disabled - And I verify that `esm-apps` is disabled - When I verify that running `pro enable ros` `with sudo` and stdin `N` exits `1` - Then stdout matches regexp - """ - ROS ESM Security Updates cannot be enabled with Ubuntu Pro: ESM Apps disabled. - Enable Ubuntu Pro: ESM Apps and proceed to enable ROS ESM Security Updates\? \(y\/N\) Cannot enable ROS ESM Security Updates when Ubuntu Pro: ESM Apps is disabled. - """ - When I run `pro enable ros` `with sudo` and stdin `y` - Then stdout matches regexp - """ - One moment, checking your subscription first - ROS ESM Security Updates cannot be enabled with Ubuntu Pro: ESM Apps disabled. - Enable Ubuntu Pro: ESM Apps and proceed to enable ROS ESM Security Updates\? \(y\/N\) Enabling required service: Ubuntu Pro: ESM Apps - Ubuntu Pro: ESM Apps enabled - Updating ROS ESM Security Updates package lists - ROS ESM Security Updates enabled - """ - And I verify that `ros` is enabled - And I verify that `esm-apps` is enabled - And I verify that `esm-infra` is enabled - When I run `apt-cache policy` as non-root - Then apt-cache policy for the following url has priority `500` - """ - amd64 Packages - """ - When I apt install `python3-catkin-pkg` - Then I verify that `python3-catkin-pkg` is installed from apt source `` - - When I run `pro enable ros-updates --assume-yes` with sudo - Then I verify that `ros-updates` is enabled - When I run `apt-cache policy` as non-root - Then apt-cache policy for the following url has priority `500` - """ - amd64 Packages - """ - When I apt install `python3-catkin-pkg` - Then I verify that `python3-catkin-pkg` is installed from apt source `` - When I run `pro disable ros` `with sudo` and stdin `y` - Then stdout matches regexp - """ - ROS ESM All Updates depends on ROS ESM Security Updates. - Disable ROS ESM All Updates and proceed to disable ROS ESM Security Updates\? \(y\/N\) Disabling dependent service: ROS ESM All Updates - Updating package lists - """ - And I verify that `ros-updates` is disabled - When I run `pro enable ros-updates` `with sudo` and stdin `y` - Then stdout matches regexp - """ - One moment, checking your subscription first - ROS ESM All Updates cannot be enabled with ROS ESM Security Updates disabled. - Enable ROS ESM Security Updates and proceed to enable ROS ESM All Updates\? \(y\/N\) Enabling required service: ROS ESM Security Updates - ROS ESM Security Updates enabled - Updating ROS ESM All Updates package lists - ROS ESM All Updates enabled - """ - And I verify that `ros-updates` is enabled - And I verify that `ros` is enabled - When I run `pro disable ros-updates --assume-yes` with sudo - And I run `pro disable ros --assume-yes` with sudo - And I run `pro disable esm-apps --assume-yes` with sudo - And I run `pro disable esm-infra --assume-yes` with sudo - And I run `pro enable ros-updates --assume-yes` with sudo - Then I verify that `ros-updates` is enabled - And I verify that `ros` is enabled - And I verify that `esm-apps` is enabled - And I verify that `esm-infra` is enabled - When I run `pro detach` `with sudo` and stdin `y` - Then stdout matches regexp: - """ - Updating package lists - Updating package lists - Updating package lists - Updating package lists - This machine is now detached. - """ - And the machine is unattached - - Examples: ubuntu release - | release | machine_type | ros-security-source | ros-updates-source | - | xenial | lxd-container | https://esm.ubuntu.com/ros/ubuntu xenial-security/main | https://esm.ubuntu.com/ros-updates/ubuntu xenial-updates/main | - | bionic | lxd-container | https://esm.ubuntu.com/ros/ubuntu bionic-security/main | https://esm.ubuntu.com/ros-updates/ubuntu bionic-updates/main | - - # Overall test for overrides; in the future, when many services - # have overrides, we can consider removing this - # esm-infra is a good choice because it doesn't already have - # other overrides that would interfere with the test - Scenario: Cloud overrides for a generic aws Focal instance - Given a `focal` `aws.generic` machine with ubuntu-advantage-tools installed - When I set the machine token overlay to the following yaml - """ - machineTokenInfo: - contractInfo: - resourceEntitlements: - - type: esm-infra - overrides: - - selector: - series: focal - directives: - additionalPackages: - - some-package-focal - - selector: - cloud: aws - directives: - additionalPackages: - - some-package-aws - """ - And I attach `contract_token` with sudo and options `--no-auto-enable` - And I verify that running `pro enable esm-infra` `with sudo` exits `1` - Then stdout matches regexp: - """ - E: Unable to locate package some-package-aws - """ - - Scenario Outline: APT auth file is edited correctly on enable - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - When I run `wc -l /etc/apt/auth.conf.d/90ubuntu-advantage` with sudo - Then I will see the following on stdout: - """ - 2 /etc/apt/auth.conf.d/90ubuntu-advantage - """ - # simulate a scenario where the line should get replaced - When I run `cp /etc/apt/auth.conf.d/90ubuntu-advantage /etc/apt/auth.conf.d/90ubuntu-advantage.backup` with sudo - When I run `pro disable esm-infra` with sudo - When I run `cp /etc/apt/auth.conf.d/90ubuntu-advantage.backup /etc/apt/auth.conf.d/90ubuntu-advantage` with sudo - When I run `pro enable esm-infra` with sudo - When I run `wc -l /etc/apt/auth.conf.d/90ubuntu-advantage` with sudo - Then I will see the following on stdout: - """ - 2 /etc/apt/auth.conf.d/90ubuntu-advantage - """ - When I run `pro enable cis` with sudo - When I run `wc -l /etc/apt/auth.conf.d/90ubuntu-advantage` with sudo - Then I will see the following on stdout: - """ - 3 /etc/apt/auth.conf.d/90ubuntu-advantage - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - - Scenario Outline: Attached enable esm-apps on a machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that `esm-apps` is enabled - And I ensure apt update runs without errors - When I run `apt-cache policy` as non-root - Then apt-cache policy for the following url has priority `510` - """ - https://esm.ubuntu.com/apps/ubuntu -apps-updates/main amd64 Packages - """ - And apt-cache policy for the following url has priority `510` - """ - https://esm.ubuntu.com/apps/ubuntu -apps-security/main amd64 Packages - """ - And I ensure apt update runs without errors - When I apt install `` - And I run `apt-cache policy ` as non-root - Then stdout matches regexp: - """ - Version table: - \s*\*\*\* .* 510 - \s*510 https://esm.ubuntu.com/apps/ubuntu -apps-security/main amd64 Packages - """ - When I verify that running `pro enable esm-apps` `with sudo` exits `1` - Then stdout matches regexp - """ - One moment, checking your subscription first - Ubuntu Pro: ESM Apps is already enabled. - See: sudo pro status - """ - - Examples: ubuntu release - | release | machine_type | apps-pkg | - | xenial | lxd-container | jq | - | bionic | lxd-container | bundler | - | focal | lxd-container | ant | - - Scenario Outline: Attached enable with corrupt lock - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `pro disable esm-infra --assume-yes` with sudo - And I create the file `/var/lib/ubuntu-advantage/lock` with the following: - """ - corrupted - """ - Then I verify that running `pro enable esm-infra --assume-yes` `with sudo` exits `1` - And stderr matches regexp: - """ - There is a corrupted lock file in the system. To continue, please remove it - from the system by running: - - \$ sudo rm /var/lib/ubuntu-advantage/lock - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | + Scenario Outline: Attached enable Common Criteria service in an ubuntu lxd container + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that running `pro enable cc-eal` `as non-root` exits `1` + And I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + When I run `pro enable cc-eal` with sudo + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Configuring APT access to CC EAL2 + Updating CC EAL2 package lists + (This will download more than 500MB of packages, so may take some time.) + Updating standard Ubuntu package lists + Installing CC EAL2 packages + CC EAL2 enabled + Please follow instructions in /usr/share/doc/ubuntu-commoncriteria/README to configure EAL2 + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | bionic | wsl | + + Scenario Outline: Enable cc-eal with --access-only + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + When I run `pro enable cc-eal --access-only` with sudo + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Configuring APT access to CC EAL2 + Updating CC EAL2 package lists + Skipping installing packages: ubuntu-commoncriteria + CC EAL2 access enabled + """ + Then I verify that running `apt-get install ubuntu-commoncriteria` `with sudo` exits `0` + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + + Scenario Outline: Attached enable Common Criteria service in an ubuntu lxd container + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that running `pro enable cc-eal` `as non-root` exits `1` + And I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + When I verify that running `pro enable cc-eal` `with sudo` exits `1` + Then I will see the following on stdout: + """ + One moment, checking your subscription first + CC EAL2 is not available for Ubuntu (). + Could not enable CC EAL2. + """ + + Examples: ubuntu release + | release | machine_type | version | full_name | + | focal | lxd-container | 20.04 LTS | Focal Fossa | + | jammy | lxd-container | 22.04 LTS | Jammy Jellyfish | + | mantic | lxd-container | 23.10 | Mantic Minotaur | + + Scenario Outline: Empty series affordance means no series, null means all series + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo and options `--no-auto-enable` + When I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + resourceEntitlements: + - type: esm-infra + affordances: + series: [] + """ + When I verify that running `pro enable esm-infra` `with sudo` exits `1` + Then stdout matches regexp: + """ + One moment, checking your subscription first + Ubuntu Pro: ESM Infra is not available for Ubuntu .* + """ + When I create the file `/var/lib/ubuntu-advantage/machine-token-overlay.json` with the following: + """ + { + "machineTokenInfo": { + "contractInfo": { + "resourceEntitlements": [ + { + "type": "esm-infra", + "affordances": { + "series": null + } + } + ] + } + } + } + """ + When I verify that running `pro enable esm-infra` `with sudo` exits `0` + Then stdout matches regexp: + """ + One moment, checking your subscription first + Configuring APT access to Ubuntu Pro: ESM Infra + Updating Ubuntu Pro: ESM Infra package lists + Ubuntu Pro: ESM Infra enabled + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + Scenario Outline: Attached enable of different services using json format + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that running `pro enable foobar --format json` `as non-root` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + Then I verify that running `pro enable foobar --format json` `with sudo` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + Then I verify that running `pro enable foobar --format json --assume-yes` `as non-root` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "This command must be run as root (try using sudo).", "message_code": "nonroot-user", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + And I verify that running `pro enable foobar --format json --assume-yes` `with sudo` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"additional_info": {"invalid_service": "foobar", "operation": "enable", "service_msg": "Try "}, "message": "Cannot enable unknown service 'foobar'.\nTry ", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": ["foobar"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + And I verify that running `pro enable blah foobar --format json --assume-yes` `with sudo` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"additional_info": {"invalid_service": "blah, foobar", "operation": "enable", "service_msg": "Try "}, "message": "Cannot enable unknown service 'blah, foobar'.\nTry ", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": ["blah", "foobar"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + And I verify that running `pro enable esm-infra --format json --assume-yes` `with sudo` exits `1` + And stdout is a json matching the `ua_operation` schema + Then I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "Ubuntu Pro: ESM Infra is already enabled - nothing to do.\nSee: sudo pro status", "message_code": "service-already-enabled", "service": "esm-infra", "type": "service"}], "failed_services": ["esm-infra"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + When I run `pro disable esm-infra` with sudo + And I run `pro enable esm-infra --format json --assume-yes` with sudo + Then stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-infra"], "result": "success", "warnings": []} + """ + When I run `pro disable esm-infra` with sudo + And I verify that running `pro enable esm-infra foobar --format json --assume-yes` `with sudo` exits `1` + Then stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"additional_info": {"invalid_service": "foobar", "operation": "enable", "service_msg": "Try "}, "message": "Cannot enable unknown service 'foobar'.\nTry ", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": ["foobar"], "needs_reboot": false, "processed_services": ["esm-infra"], "result": "failure", "warnings": []} + """ + When I run `pro disable esm-infra esm-apps` with sudo + And I run `pro enable esm-infra esm-apps --beta --format json --assume-yes` with sudo + Then stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-apps", "esm-infra"], "result": "success", "warnings": []} + """ + + Examples: ubuntu release + | release | machine_type | valid_services | + | xenial | lxd-container | anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | + | bionic | lxd-container | anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | + | focal | lxd-container | anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | + | jammy | lxd-container | anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | + | noble | lxd-container | anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | + + Scenario Outline: Attached enable of a service in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that running `pro enable foobar` `as non-root` exits `1` + And I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + And I verify that running `pro enable foobar` `with sudo` exits `1` + And stdout matches regexp: + """ + One moment, checking your subscription first + Cannot enable unknown service 'foobar'. + + """ + And I verify that running `pro enable blah foobar` `with sudo` exits `1` + And stdout matches regexp: + """ + One moment, checking your subscription first + Cannot enable unknown service 'blah, foobar'. + + """ + And I verify that running `pro enable esm-infra` `with sudo` exits `1` + And I will see the following on stdout: + """ + One moment, checking your subscription first + Ubuntu Pro: ESM Infra is already enabled - nothing to do. + See: sudo pro status + Could not enable Ubuntu Pro: ESM Infra. + """ + When I run `apt-cache policy` with sudo + Then apt-cache policy for the following url has priority `510` + """ + -infra-updates/main amd64 Packages + """ + And apt-cache policy for the following url has priority `510` + """ + -infra-security/main amd64 Packages + """ + And I ensure apt update runs without errors + When I apt install `` + And I run `apt-cache policy ` as non-root + Then stdout matches regexp: + """ + \s*510 -infra-security/main amd64 Packages + """ + + Examples: ubuntu release + | release | machine_type | infra-pkg | esm-infra-url | msg | + | xenial | lxd-container | libkrad0 | https://esm.ubuntu.com/infra/ubuntu | Try anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | + | bionic | lxd-container | libkrad0 | https://esm.ubuntu.com/infra/ubuntu | Try anbox-cloud, cc-eal, cis, esm-apps, esm-infra, fips, fips-preview,\nfips-updates, landscape, livepatch, realtime-kernel, ros, ros-updates. | + | focal | lxd-container | hello | https://esm.ubuntu.com/infra/ubuntu | Try anbox-cloud, cc-eal, esm-apps, esm-infra, fips, fips-preview, fips-updates,\nlandscape, livepatch, realtime-kernel, ros, ros-updates, usg. | + + Scenario Outline: Attached enable of non-container services in a ubuntu lxd container + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that running `pro enable livepatch` `as non-root` exits `1` + And I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + And I verify that running `pro enable livepatch` `with sudo` exits `1` + And I will see the following on stdout: + """ + One moment, checking your subscription first + Cannot install Livepatch on a container. + Could not enable Livepatch. + """ + + Examples: Un-supported services in containers + | release | machine_type | + | bionic | lxd-container | + | focal | lxd-container | + | xenial | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: Attached enable not entitled service in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + resourceEntitlements: + - type: esm-apps + entitled: false + """ + When I attach `contract_token` with sudo + Then I verify that running `pro enable esm-apps` `as non-root` exits `1` + And I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + And I verify that running `pro enable esm-apps --beta` `with sudo` exits `1` + And I will see the following on stdout: + """ + One moment, checking your subscription first + This subscription is not entitled to Ubuntu Pro: ESM Apps + View your subscription at: https://ubuntu.com/pro/dashboard + Could not enable Ubuntu Pro: ESM Apps. + """ + + Examples: not entitled services + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + Scenario Outline: Attached enable of cis service in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I verify that running `pro enable cis --access-only` `with sudo` exits `0` + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Configuring APT access to CIS Audit + Updating CIS Audit package lists + Skipping installing packages: usg-cisbenchmark usg-common + CIS Audit access enabled + Visit https://ubuntu.com/security/cis to learn how to use CIS + """ + When I run `pro disable cis` with sudo + And I verify that running `pro enable cis` `with sudo` exits `0` + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Configuring APT access to CIS Audit + Updating CIS Audit package lists + Updating standard Ubuntu package lists + Installing CIS Audit packages + CIS Audit enabled + Visit https://ubuntu.com/security/cis to learn how to use CIS + """ + When I run `apt-cache policy usg-cisbenchmark` as non-root + Then stdout does not match regexp: + """ + .*Installed: \(none\) + """ + And stdout matches regexp: + """ + \s* 500 https://esm.ubuntu.com/cis/ubuntu /main amd64 Packages + """ + When I run `apt-cache policy usg-common` as non-root + Then stdout does not match regexp: + """ + .*Installed: \(none\) + """ + And stdout matches regexp: + """ + \s* 500 https://esm.ubuntu.com/cis/ubuntu /main amd64 Packages + """ + When I verify that running `pro enable cis` `with sudo` exits `1` + Then stdout matches regexp + """ + One moment, checking your subscription first + CIS Audit is already enabled - nothing to do. + See: sudo pro status + """ + When I run `cis-audit level1_server` with sudo + Then stdout matches regexp + """ + Title.*Ensure no duplicate UIDs exist + Rule.*xccdf_com.ubuntu..cis_rule_CIS-.* + Result.*pass + """ + And stdout matches regexp: + """ + Title.*Ensure default user umask is 027 or more restrictive + Rule.*xccdf_com.ubuntu..cis_rule_CIS-.* + Result.*fail + """ + And stdout matches regexp + """ + CIS audit scan completed + """ + When I verify that running `/usr/share/ubuntu-scap-security-guides/cis-hardening/ lvl1_server` `with sudo` exits `0` + And I run `cis-audit level1_server` with sudo + Then stdout matches regexp: + """ + Title.*Ensure default user umask is 027 or more restrictive + Rule.*xccdf_com.ubuntu..cis_rule_CIS-.* + Result.*pass + """ + And stdout matches regexp + """ + CIS audit scan completed + """ + + Examples: cis script + | release | machine_type | cis_script | + | bionic | lxd-container | Canonical_Ubuntu_18.04_CIS-harden.sh | + | bionic | wsl | Canonical_Ubuntu_18.04_CIS-harden.sh | + | xenial | lxd-container | Canonical_Ubuntu_16.04_CIS_v1.1.0-harden.sh | + + Scenario Outline: Attached enable of cis service in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I verify that running `pro enable cis` `with sudo` exits `0` + Then I will see the following on stdout: + """ + One moment, checking your subscription first + From Ubuntu 20.04 onward 'pro enable cis' has been + replaced by 'pro enable usg'. See more information at: + https://ubuntu.com/security/certifications/docs/usg + Configuring APT access to CIS Audit + Updating CIS Audit package lists + Updating standard Ubuntu package lists + Installing CIS Audit packages + CIS Audit enabled + Visit https://ubuntu.com/security/cis to learn how to use CIS + """ + When I run `apt-cache policy usg-cisbenchmark` as non-root + Then stdout does not match regexp: + """ + .*Installed: \(none\) + """ + And stdout matches regexp: + """ + \s* 500 https://esm.ubuntu.com/cis/ubuntu /main amd64 Packages + """ + When I run `apt-cache policy usg-common` as non-root + Then stdout does not match regexp: + """ + .*Installed: \(none\) + """ + And stdout matches regexp: + """ + \s* 500 https://esm.ubuntu.com/cis/ubuntu /main amd64 Packages + """ + When I verify that running `pro enable cis` `with sudo` exits `1` + Then stdout matches regexp + """ + One moment, checking your subscription first + From Ubuntu 20.04 onward 'pro enable cis' has been + replaced by 'pro enable usg'. See more information at: + https://ubuntu.com/security/certifications/docs/usg + CIS Audit is already enabled - nothing to do. + See: sudo pro status + """ + When I run `cis-audit level1_server` with sudo + Then stdout matches regexp + """ + Title.*Ensure no duplicate UIDs exist + Rule.*xccdf_com.ubuntu..cis_rule_CIS-.* + Result.*pass + """ + And stdout matches regexp: + """ + Title.*Ensure default user umask is 027 or more restrictive + Rule.*xccdf_com.ubuntu..cis_rule_CIS-.* + Result.*fail + """ + And stdout matches regexp + """ + CIS audit scan completed + """ + When I verify that running `/usr/share/ubuntu-scap-security-guides/cis-hardening/ lvl1_server` `with sudo` exits `0` + And I run `cis-audit level1_server` with sudo + Then stdout matches regexp: + """ + Title.*Ensure default user umask is 027 or more restrictive + Rule.*xccdf_com.ubuntu..cis_rule_CIS-.* + Result.*pass + """ + And stdout matches regexp + """ + CIS audit scan completed + """ + + Examples: cis service + | release | machine_type | cis_script | + | focal | lxd-container | Canonical_Ubuntu_20.04_CIS-harden.sh | + | focal | wsl | Canonical_Ubuntu_20.04_CIS-harden.sh | + + Scenario Outline: Attached enable of usg service in a focal machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `pro enable usg` with sudo + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Configuring APT access to Ubuntu Security Guide + Updating Ubuntu Security Guide package lists + Ubuntu Security Guide enabled + Visit https://ubuntu.com/security/certifications/docs/usg for the next steps + """ + And I verify that `usg` is enabled + When I run `pro disable usg` with sudo + Then stdout matches regexp: + """ + Updating package lists + """ + And I verify that `usg` is disabled + When I run `pro enable cis` with sudo + Then I will see the following on stdout: + """ + One moment, checking your subscription first + From Ubuntu 20.04 onward 'pro enable cis' has been + replaced by 'pro enable usg'. See more information at: + https://ubuntu.com/security/certifications/docs/usg + Configuring APT access to CIS Audit + Updating CIS Audit package lists + Updating standard Ubuntu package lists + Installing CIS Audit packages + CIS Audit enabled + Visit https://ubuntu.com/security/cis to learn how to use CIS + """ + And I verify that `usg` is enabled + When I run `pro disable usg` with sudo + Then stdout matches regexp: + """ + Updating package lists + """ + And I verify that `usg` is disabled + + Examples: cis service + | release | machine_type | + | focal | lxd-container | + | focal | wsl | + + Scenario Outline: Attached disable of livepatch in a lxd vm + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that `esm-apps` is enabled + And I verify that `esm-infra` is enabled + And I verify that `livepatch` status is `` + When I run `pro disable livepatch` with sudo + Then I verify that running `canonical-livepatch status` `with sudo` exits `1` + And stderr matches regexp: + """ + Machine is not enabled. Please run 'sudo canonical-livepatch enable' with the + token obtained from https://ubuntu.com/livepatch. + """ + And I verify that `esm-apps` is enabled + And I verify that `esm-infra` is enabled + And I verify that `livepatch` is disabled + When I verify that running `pro enable livepatch --access-only` `with sudo` exits `1` + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Livepatch does not support being enabled with --access-only + Could not enable Livepatch. + """ + + Examples: ubuntu release + | release | machine_type | livepatch_status | + | xenial | lxd-vm | warning | + | bionic | lxd-vm | enabled | + + Scenario Outline: Attach works when snapd cannot be installed + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt remove `snapd` + And I create the file `/etc/apt/preferences.d/no-snapd` with the following + """ + Package: snapd + Pin: release o=* + Pin-Priority: -10 + """ + And I apt update + When I attempt to attach `contract_token` with sudo + Then I will see the following on stderr: + """ + Failed to enable default services, check: sudo pro status + """ + And I verify that `livepatch` is disabled + And I verify that running `pro enable livepatch` `with sudo` exits `1` + And I will see the following on stdout: + """ + One moment, checking your subscription first + Installing Livepatch + Installing snapd + Updating standard Ubuntu package lists + Failed to install snapd on the system + Could not enable Livepatch. + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-vm | + | bionic | lxd-vm | + + Scenario Outline: Attached enable livepatch + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify that running `canonical-livepatch status` `with sudo` exits `1` + Then I will see the following on stderr: + """ + sudo: canonical-livepatch: command not found + """ + When I attach `contract_token` with sudo + Then stdout matches regexp: + """ + Installing canonical-livepatch snap + Canonical Livepatch enabled + """ + And I verify that `livepatch` status is `` + When I run `canonical-livepatch status` with sudo + Then stdout matches regexp: + """ + running: true + """ + + Examples: ubuntu release + | release | machine_type | livepatch_status | + | xenial | lxd-vm | warning | + | bionic | lxd-vm | enabled | + + Scenario Outline: Attached enable livepatch + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then stdout matches regexp: + """ + Installing canonical-livepatch snap + Canonical Livepatch enabled + """ + And I verify that `livepatch` status is warning + When I run `pro api u.pro.security.status.reboot_required.v1` with sudo + Then stdout matches regexp: + """ + {"_schema_version": "v1", "data": {"attributes": {"livepatch_enabled": true, "livepatch_enabled_and_kernel_patched": true, "livepatch_state": "applied", "livepatch_support": "kernel-upgrade-required", "reboot_required": "no", "reboot_required_packages": {"kernel_packages": null, "standard_packages": null}}, "meta": {"environment_vars": \[\]}, "type": "RebootRequired"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} + """ + When I run `pro system reboot-required` as non-root + Then I will see the following on stdout: + """ + no + """ + When I apt install `libc6` + And I run `pro api u.pro.security.status.reboot_required.v1` as non-root + Then stdout matches regexp: + """ + {"_schema_version": "v1", "data": {"attributes": {"livepatch_enabled": true, "livepatch_enabled_and_kernel_patched": true, "livepatch_state": "applied", "livepatch_support": "kernel-upgrade-required", "reboot_required": "yes", "reboot_required_packages": {"kernel_packages": \[\], "standard_packages": \["libc6"\]}}, "meta": {"environment_vars": \[\]}, "type": "RebootRequired"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} + """ + When I run `pro system reboot-required` as non-root + Then I will see the following on stdout: + """ + yes + """ + When I reboot the machine + And I run `pro system reboot-required` as non-root + Then I will see the following on stdout: + """ + no + """ + When I apt install `linux-image-generic` + And I run `pro api u.pro.security.status.reboot_required.v1` as non-root + Then stdout matches regexp: + """ + {"_schema_version": "v1", "data": {"attributes": {"livepatch_enabled": true, "livepatch_enabled_and_kernel_patched": true, "livepatch_state": "applied", "livepatch_support": "kernel-upgrade-required", "reboot_required": "yes", "reboot_required_packages": {"kernel_packages": \["linux-base"\], "standard_packages": \[\]}}, "meta": {"environment_vars": \[\]}, "type": "RebootRequired"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} + """ + When I run `pro system reboot-required` as non-root + Then I will see the following on stdout: + """ + yes + """ + When I apt install `dbus` + And I run `pro api u.pro.security.status.reboot_required.v1` with sudo + Then stdout matches regexp: + """ + {"_schema_version": "v1", "data": {"attributes": {"livepatch_enabled": true, "livepatch_enabled_and_kernel_patched": true, "livepatch_state": "applied", "livepatch_support": "kernel-upgrade-required", "reboot_required": "yes", "reboot_required_packages": {"kernel_packages": \["linux-base"\], "standard_packages": \["dbus"\]}}, "meta": {"environment_vars": \[\]}, "type": "RebootRequired"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} + """ + When I run `pro system reboot-required` as non-root + Then I will see the following on stdout: + """ + yes + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-vm | + + @slow + Scenario: Attached enable livepatch on a machine with fips active + Given a `bionic` `lxd-vm` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then stdout matches regexp: + """ + Enabling Ubuntu Pro: ESM Infra + Ubuntu Pro: ESM Infra enabled + Enabling Livepatch + Livepatch enabled + """ + When I run `pro disable livepatch` with sudo + And I run `pro enable fips --assume-yes` with sudo + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Configuring APT access to FIPS + Updating FIPS package lists + Updating standard Ubuntu package lists + Installing FIPS packages + FIPS enabled + A reboot is required to complete install. + """ + When I append the following on uaclient config: + """ + features: + block_disable_on_enable: true + """ + Then I verify that running `pro enable livepatch` `with sudo` exits `1` + And I will see the following on stdout + """ + One moment, checking your subscription first + Cannot enable Livepatch when FIPS is enabled. + Could not enable Livepatch. + """ + Then I verify that running `pro enable livepatch --format json --assume-yes` `with sudo` exits `1` + And I will see the following on stdout + """ + {"_schema_version": "0.1", "errors": [{"message": "Cannot enable Livepatch when FIPS is enabled.", "message_code": "livepatch-error-when-fips-enabled", "service": "livepatch", "type": "service"}], "failed_services": ["livepatch"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + + Scenario: Attached enable fips on a machine with livepatch active + Given a `bionic` `lxd-vm` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then stdout matches regexp: + """ + Enabling Ubuntu Pro: ESM Infra + Ubuntu Pro: ESM Infra enabled + Enabling Livepatch + Livepatch enabled + """ + When I append the following on uaclient config: + """ + features: + block_disable_on_enable: true + """ + Then I verify that running `pro enable fips --assume-yes` `with sudo` exits `1` + And I will see the following on stdout + """ + One moment, checking your subscription first + Cannot enable Livepatch when FIPS is enabled. + Could not enable Livepatch. + """ + Then I verify that running `pro enable fips --assume-yes --format json` `with sudo` exits `1` + And stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "Cannot enable FIPS when Livepatch is enabled.", "message_code": "incompatible-service-stops-enable", "service": "fips", "type": "service"}], "failed_services": ["fips"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + + @slow + Scenario Outline: Attached enable fips on a machine with livepatch active + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then stdout matches regexp: + """ + Ubuntu Pro: ESM Infra enabled + """ + And stdout matches regexp: + """ + Enabling Livepatch + Livepatch enabled + """ + When I run `pro enable fips --assume-yes` with sudo + Then I will see the following on stdout + """ + One moment, checking your subscription first + Disabling incompatible service: Livepatch + Updating FIPS package lists + Installing FIPS packages + Updating standard Ubuntu package lists + FIPS enabled + A reboot is required to complete install. + """ + When I run `pro status --all` with sudo + Then stdout matches regexp: + """ + fips +yes +enabled + """ + And stdout matches regexp: + """ + livepatch +yes +n/a + """ + + Examples: ubuntu release + | release | machine_type | + | bionic | lxd-vm | + | xenial | lxd-vm | + + @slow + Scenario Outline: Attached enable fips on a machine with fips-updates active + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then stdout matches regexp: + """ + Ubuntu Pro: ESM Infra enabled + """ + And stdout matches regexp: + """ + Enabling Livepatch + Livepatch enabled + """ + When I run `pro disable livepatch` with sudo + And I run `pro enable fips-updates --assume-yes` with sudo + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Updating FIPS Updates package lists + Installing FIPS Updates packages + Updating standard Ubuntu package lists + FIPS Updates enabled + A reboot is required to complete install. + """ + When I verify that running `pro enable fips --assume-yes` `with sudo` exits `1` + Then I will see the following on stdout + """ + One moment, checking your subscription first + Cannot enable Livepatch when FIPS is enabled. + Could not enable Livepatch. + """ + + Examples: ubuntu release + | release | machine_type | + | bionic | lxd-vm | + | xenial | lxd-vm | + + Scenario Outline: Attached enable ros on a machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that `ros` is disabled + When I run `pro enable ros --assume-yes` with sudo + Then I verify that `ros` is enabled + And I verify that `esm-apps` is enabled + And I verify that `esm-infra` is enabled + When I verify that running `pro disable esm-apps` `with sudo` and stdin `N` exits `1` + Then stdout matches regexp + """ + ROS ESM Security Updates depends on Ubuntu Pro: ESM Apps. + Disable ROS ESM Security Updates and proceed to disable Ubuntu Pro: ESM Apps\? \(y\/N\) Cannot disable Ubuntu Pro: ESM Apps when ROS ESM Security Updates is enabled. + """ + When I run `pro disable esm-apps` `with sudo` and stdin `y` + Then stdout matches regexp + """ + ROS ESM Security Updates depends on Ubuntu Pro: ESM Apps. + Disable ROS ESM Security Updates and proceed to disable Ubuntu Pro: ESM Apps\? \(y\/N\) Disabling dependent service: ROS ESM Security Updates + Removing APT access to ROS ESM Security Updates + Updating package lists + Removing APT access to Ubuntu Pro: ESM Apps + Updating package lists + """ + And I verify that `ros` is disabled + And I verify that `esm-apps` is disabled + When I verify that running `pro enable ros` `with sudo` and stdin `N` exits `1` + Then stdout matches regexp + """ + ROS ESM Security Updates cannot be enabled with Ubuntu Pro: ESM Apps disabled. + Enable Ubuntu Pro: ESM Apps and proceed to enable ROS ESM Security Updates\? \(y\/N\) Cannot enable ROS ESM Security Updates when Ubuntu Pro: ESM Apps is disabled. + """ + When I run `pro enable ros` `with sudo` and stdin `y` + Then stdout matches regexp + """ + One moment, checking your subscription first + ROS ESM Security Updates cannot be enabled with Ubuntu Pro: ESM Apps disabled. + Enable Ubuntu Pro: ESM Apps and proceed to enable ROS ESM Security Updates\? \(y\/N\) Enabling required service: Ubuntu Pro: ESM Apps + Configuring APT access to Ubuntu Pro: ESM Apps + Updating Ubuntu Pro: ESM Apps package lists + Configuring APT access to ROS ESM Security Updates + Updating ROS ESM Security Updates package lists + ROS ESM Security Updates enabled + """ + And I verify that `ros` is enabled + And I verify that `esm-apps` is enabled + And I verify that `esm-infra` is enabled + When I run `apt-cache policy` as non-root + Then apt-cache policy for the following url has priority `500` + """ + amd64 Packages + """ + When I apt install `python3-catkin-pkg` + Then I verify that `python3-catkin-pkg` is installed from apt source `` + When I run `pro enable ros-updates --assume-yes` with sudo + Then I verify that `ros-updates` is enabled + When I run `apt-cache policy` as non-root + Then apt-cache policy for the following url has priority `500` + """ + amd64 Packages + """ + When I apt install `python3-catkin-pkg` + Then I verify that `python3-catkin-pkg` is installed from apt source `` + When I run `pro disable ros` `with sudo` and stdin `y` + Then stdout matches regexp + """ + ROS ESM All Updates depends on ROS ESM Security Updates. + Disable ROS ESM All Updates and proceed to disable ROS ESM Security Updates\? \(y\/N\) Disabling dependent service: ROS ESM All Updates + Removing APT access to ROS ESM All Updates + Updating package lists + Removing APT access to ROS ESM Security Updates + Updating package lists + """ + And I verify that `ros-updates` is disabled + When I run `pro enable ros-updates` `with sudo` and stdin `y` + Then stdout matches regexp + """ + One moment, checking your subscription first + ROS ESM All Updates cannot be enabled with ROS ESM Security Updates disabled. + Enable ROS ESM Security Updates and proceed to enable ROS ESM All Updates\? \(y\/N\) Enabling required service: ROS ESM Security Updates + Configuring APT access to ROS ESM Security Updates + Updating ROS ESM Security Updates package lists + Configuring APT access to ROS ESM All Updates + Updating ROS ESM All Updates package lists + ROS ESM All Updates enabled + """ + And I verify that `ros-updates` is enabled + And I verify that `ros` is enabled + When I run `pro disable ros-updates --assume-yes` with sudo + And I run `pro disable ros --assume-yes` with sudo + And I run `pro disable esm-apps --assume-yes` with sudo + And I run `pro disable esm-infra --assume-yes` with sudo + And I run `pro enable ros-updates --assume-yes` with sudo + Then I verify that `ros-updates` is enabled + And I verify that `ros` is enabled + And I verify that `esm-apps` is enabled + And I verify that `esm-infra` is enabled + When I run `pro detach` `with sudo` and stdin `y` + Then stdout matches regexp: + """ + Removing APT access to ROS ESM All Updates + Updating package lists + Removing APT access to ROS ESM Security Updates + Updating package lists + Removing APT access to Ubuntu Pro: ESM Apps + Updating package lists + Removing APT access to Ubuntu Pro: ESM Infra + Updating package lists + This machine is now detached. + """ + And the machine is unattached + + Examples: ubuntu release + | release | machine_type | ros-security-source | ros-updates-source | + | xenial | lxd-container | https://esm.ubuntu.com/ros/ubuntu xenial-security/main | https://esm.ubuntu.com/ros-updates/ubuntu xenial-updates/main | + | bionic | lxd-container | https://esm.ubuntu.com/ros/ubuntu bionic-security/main | https://esm.ubuntu.com/ros-updates/ubuntu bionic-updates/main | + | bionic | wsl | https://esm.ubuntu.com/ros/ubuntu bionic-security/main | https://esm.ubuntu.com/ros-updates/ubuntu bionic-updates/main | + + # Overall test for overrides; in the future, when many services + # have overrides, we can consider removing this + # esm-infra is a good choice because it doesn't already have + # other overrides that would interfere with the test + Scenario: Cloud overrides for a generic aws Focal instance + Given a `focal` `aws.generic` machine with ubuntu-advantage-tools installed + When I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + resourceEntitlements: + - type: esm-infra + overrides: + - selector: + series: focal + directives: + additionalPackages: + - some-package-focal + - selector: + cloud: aws + directives: + additionalPackages: + - some-package-aws + """ + And I attach `contract_token` with sudo and options `--no-auto-enable` + And I verify that running `pro enable esm-infra` `with sudo` exits `1` + Then stdout matches regexp: + """ + E: Unable to locate package some-package-aws + """ + + Scenario Outline: APT auth file is edited correctly on enable + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + When I run `wc -l /etc/apt/auth.conf.d/90ubuntu-advantage` with sudo + Then I will see the following on stdout: + """ + 2 /etc/apt/auth.conf.d/90ubuntu-advantage + """ + # simulate a scenario where the line should get replaced + When I run `cp /etc/apt/auth.conf.d/90ubuntu-advantage /etc/apt/auth.conf.d/90ubuntu-advantage.backup` with sudo + When I run `pro disable esm-infra` with sudo + When I run `cp /etc/apt/auth.conf.d/90ubuntu-advantage.backup /etc/apt/auth.conf.d/90ubuntu-advantage` with sudo + When I run `pro enable esm-infra` with sudo + When I run `wc -l /etc/apt/auth.conf.d/90ubuntu-advantage` with sudo + Then I will see the following on stdout: + """ + 2 /etc/apt/auth.conf.d/90ubuntu-advantage + """ + When I run `pro enable cis` with sudo + When I run `wc -l /etc/apt/auth.conf.d/90ubuntu-advantage` with sudo + Then I will see the following on stdout: + """ + 3 /etc/apt/auth.conf.d/90ubuntu-advantage + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + + Scenario Outline: Attached enable esm-apps on a machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that `esm-apps` is enabled + And I ensure apt update runs without errors + When I run `apt-cache policy` as non-root + Then apt-cache policy for the following url has priority `510` + """ + https://esm.ubuntu.com/apps/ubuntu -apps-updates/main amd64 Packages + """ + And apt-cache policy for the following url has priority `510` + """ + https://esm.ubuntu.com/apps/ubuntu -apps-security/main amd64 Packages + """ + And I ensure apt update runs without errors + When I apt install `` + And I run `apt-cache policy ` as non-root + Then stdout matches regexp: + """ + Version table: + \s*\*\*\* .* 510 + \s*510 https://esm.ubuntu.com/apps/ubuntu -apps-security/main amd64 Packages + """ + When I verify that running `pro enable esm-apps` `with sudo` exits `1` + Then stdout matches regexp + """ + One moment, checking your subscription first + Ubuntu Pro: ESM Apps is already enabled - nothing to do. + See: sudo pro status + """ + + Examples: ubuntu release + | release | machine_type | apps-pkg | + | xenial | lxd-container | jq | + | bionic | lxd-container | bundler | + | focal | lxd-container | ant | + + Scenario Outline: Attached enable with corrupt lock + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `pro disable esm-infra --assume-yes` with sudo + And I create the file `/var/lib/ubuntu-advantage/lock` with the following: + """ + corrupted + """ + Then I verify that running `pro enable esm-infra --assume-yes` `with sudo` exits `1` + And stderr matches regexp: + """ + There is a corrupted lock file in the system. To continue, please remove it + from the system by running: + + \$ sudo rm /var/lib/ubuntu-advantage/lock + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/attached_status.feature ubuntu-advantage-tools-32~16.04/features/attached_status.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/attached_status.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/attached_status.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,428 +1,480 @@ Feature: Attached status - @uses.config.contract_token - Scenario Outline: Attached status in a ubuntu machine - formatted - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `pro status --format json` as non-root - Then stdout is a json matching the `ua_status` schema - When I run `pro status --format yaml` as non-root - Then stdout is a yaml matching the `ua_status` schema - - When I create the file `/var/lib/ubuntu-advantage/machine-token-overlay.json` with the following: - """ - { - "machineTokenInfo": { - "contractInfo": { - "effectiveTo": null - } - } - } - """ - And I append the following on uaclient config: - """ - features: - machine_token_overlay: "/var/lib/ubuntu-advantage/machine-token-overlay.json" - """ - And I run `pro status` with sudo - Then stdout contains substring: - """ - Valid until: Unknown/Expired - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - @uses.config.contract_token - Scenario Outline: Non-root status can see in-progress operations - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - When I run shell command `sudo pro enable cis >/dev/null & pro status` as non-root - Then stdout matches regexp: - """ - NOTICES - Operation in progress: pro enable - """ - When I run `pro status --wait` as non-root - When I run `pro disable cis --assume-yes` with sudo - When I run shell command `sudo pro enable cis & pro status --wait` as non-root - Then stdout matches regexp: - """ - One moment, checking your subscription first - Updating CIS Audit package lists - Updating standard Ubuntu package lists - Installing CIS Audit packages - CIS Audit enabled - Visit https://ubuntu.com/security/cis to learn how to use CIS - \.+ - SERVICE +ENTITLED +STATUS +DESCRIPTION - """ - Then stdout does not match regexp: - """ - NOTICES - Operation in progress: pro enable - """ - When I run `pro disable cis --assume-yes` with sudo - When I apt install `jq` - When I run shell command `sudo pro enable cis >/dev/null & pro status --format json | jq -r .execution_status` as non-root - Then I will see the following on stdout: - """ - active - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - - Scenario Outline: Attached status in a ubuntu Pro machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - log_level: debug - """ - And I run `pro auto-attach` with sudo - When I run `pro status` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages - cis +yes +disabled +Security compliance and audit tools - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips +yes +disabled +NIST-certified FIPS crypto packages - fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates - livepatch +yes +enabled +Canonical Livepatch service - """ - When I run `pro status --all` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +yes +n/a +Scalable Android in the cloud - cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages - cis +yes +disabled +Security compliance and audit tools - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips +yes +disabled +NIST-certified FIPS crypto packages - fips-preview +yes +n/a +Preview of FIPS crypto packages undergoing certification with NIST - fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates - livepatch +yes +enabled +Canonical Livepatch service - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | aws.pro | - | xenial | azure.pro | - - Scenario Outline: Attached status in a ubuntu Pro machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - log_level: debug - """ - And I run `pro auto-attach` with sudo - And I verify root and non-root `pro status` calls have the same output - And I run `pro status` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages - cis +yes +disabled +Security compliance and audit tools - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - livepatch +yes +warning +Current kernel is not supported - """ - When I verify root and non-root `pro status --all` calls have the same output - And I run `pro status --all` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +yes +n/a +Scalable Android in the cloud - cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages - cis +yes +disabled +Security compliance and audit tools - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips +yes +n/a +NIST-certified FIPS crypto packages - fips-preview +yes +n/a +Preview of FIPS crypto packages undergoing certification with NIST - fips-updates +yes +n/a +FIPS compliant crypto packages with stable security updates - livepatch +yes +warning +Current kernel is not supported - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | gcp.pro | - - Scenario Outline: Attached status in a ubuntu Pro machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - log_level: debug - """ - And I run `pro auto-attach` with sudo - And I verify root and non-root `pro status` calls have the same output - And I run `pro status` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages - cis +yes +disabled +Security compliance and audit tools - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips +yes +disabled +NIST-certified FIPS crypto packages - fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates - livepatch +yes +enabled +Canonical Livepatch service - """ - When I verify root and non-root `pro status --all` calls have the same output - And I run `pro status --all` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +yes +n/a +Scalable Android in the cloud - cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages - cis +yes +disabled +Security compliance and audit tools - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips +yes +disabled +NIST-certified FIPS crypto packages - fips-preview +yes +n/a +Preview of FIPS crypto packages undergoing certification with NIST - fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates - livepatch +yes +enabled +Canonical Livepatch service - """ - - Examples: ubuntu release - | release | machine_type | - | bionic | aws.pro | - | bionic | azure.pro | - | bionic | gcp.pro | - - Scenario Outline: Attached status in a ubuntu Pro machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - log_level: debug - """ - And I run `pro auto-attach` with sudo - And I verify root and non-root `pro status` calls have the same output - And I run `pro status` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +yes +disabled +Scalable Android in the cloud - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips +yes +disabled +NIST-certified FIPS crypto packages - fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates - livepatch +yes +enabled +Canonical Livepatch service - usg +yes +disabled +Security compliance and audit tools - """ - When I verify root and non-root `pro status --all` calls have the same output - And I run `pro status --all` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +yes +disabled +Scalable Android in the cloud - cc-eal +yes +n/a +Common Criteria EAL2 Provisioning Packages - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips +yes +disabled +NIST-certified FIPS crypto packages - fips-preview +yes +n/a +Preview of FIPS crypto packages undergoing certification with NIST - fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates - livepatch +yes +enabled +Canonical Livepatch service - usg +yes +disabled +Security compliance and audit tools - """ - - Examples: ubuntu release - | release | machine_type | - | focal | aws.pro | - | focal | azure.pro | - | focal | gcp.pro | - - Scenario Outline: Attached status in a ubuntu Pro machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - log_level: debug - """ - And I run `pro auto-attach` with sudo - And I verify root and non-root `pro status` calls have the same output - And I run `pro status` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +yes +disabled +Scalable Android in the cloud - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips-preview +yes +disabled +Preview of FIPS crypto packages undergoing certification with NIST - fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates - livepatch +yes +enabled +Canonical Livepatch service - usg +yes +disabled +Security compliance and audit tools - """ - When I verify root and non-root `pro status --all` calls have the same output - And I run `pro status --all` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +yes +disabled +Scalable Android in the cloud - cc-eal +yes +n/a +Common Criteria EAL2 Provisioning Packages - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips +yes +n/a +NIST-certified FIPS crypto packages - fips-preview +yes +disabled +Preview of FIPS crypto packages undergoing certification with NIST - fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates - livepatch +yes +enabled +Canonical Livepatch service - usg +yes +disabled +Security compliance and audit tools - """ - - Examples: ubuntu release - | release | machine_type | - | jammy | aws.pro | - | jammy | azure.pro | - | jammy | gcp.pro | - - @uses.config.contract_token - Scenario Outline: Attached status in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I verify root and non-root `pro status` calls have the same output - And I run `pro status` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages - cis +yes +disabled +Security compliance and audit tools - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips +yes +disabled +NIST-certified FIPS crypto packages - fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates - ros +yes +disabled +Security Updates for the Robot Operating System - ros-updates +yes +disabled +All Updates for the Robot Operating System - - For a list of all Ubuntu Pro services, run 'pro status --all' - Enable services with: pro enable - """ - When I verify root and non-root `pro status --all` calls have the same output - And I run `pro status --all` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +yes +n/a +.* - cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages - cis +yes +disabled +Security compliance and audit tools - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips +yes +disabled +NIST-certified FIPS crypto packages - fips-preview +yes +n/a +Preview of FIPS crypto packages undergoing certification with NIST - fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates - landscape +yes +n/a +Management and administration tool for Ubuntu - livepatch +yes +n/a +Canonical Livepatch service - realtime-kernel +yes +n/a +Ubuntu kernel with PREEMPT_RT patches integrated - ros +yes +disabled +Security Updates for the Robot Operating System - ros-updates +yes +disabled +All Updates for the Robot Operating System - - Enable services with: pro enable - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - - @uses.config.contract_token - Scenario Outline: Attached status in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I verify root and non-root `pro status` calls have the same output - And I run `pro status` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +yes +disabled +.* - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips +yes +disabled +NIST-certified FIPS crypto packages - fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates - ros +yes +disabled +Security Updates for the Robot Operating System - usg +yes +disabled +Security compliance and audit tools - - For a list of all Ubuntu Pro services, run 'pro status --all' - Enable services with: pro enable - """ - When I verify root and non-root `pro status --all` calls have the same output - And I run `pro status --all` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +yes +disabled +.* - cc-eal +yes +n/a +Common Criteria EAL2 Provisioning Packages - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips +yes +disabled +NIST-certified FIPS crypto packages - fips-preview +yes +n/a +Preview of FIPS crypto packages undergoing certification with NIST - fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates - landscape +yes +n/a +Management and administration tool for Ubuntu - livepatch +yes +n/a +Canonical Livepatch service - realtime-kernel +yes +n/a +Ubuntu kernel with PREEMPT_RT patches integrated - ros +yes +disabled +Security Updates for the Robot Operating System - ros-updates +yes +n/a +All Updates for the Robot Operating System - usg +yes +disabled +Security compliance and audit tools - - Enable services with: pro enable - """ - - Examples: ubuntu release - | release | machine_type | - | focal | lxd-container | - - @uses.config.contract_token - Scenario Outline: Attached status in the latest LTS ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I verify root and non-root `pro status` calls have the same output - And I run `pro status` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +yes +disabled +.* - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips-preview +yes +disabled +Preview of FIPS crypto packages undergoing certification with NIST - fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates - usg +yes +disabled +Security compliance and audit tools - - For a list of all Ubuntu Pro services, run 'pro status --all' - Enable services with: pro enable - """ - When I verify root and non-root `pro status --all` calls have the same output - And I run `pro status --all` as non-root - Then stdout matches regexp: - """ - SERVICE +ENTITLED +STATUS +DESCRIPTION - anbox-cloud +yes +disabled +.* - cc-eal +yes +n/a +Common Criteria EAL2 Provisioning Packages - esm-apps +yes +enabled +Expanded Security Maintenance for Applications - esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure - fips +yes +n/a +NIST-certified FIPS crypto packages - fips-preview +yes +disabled +Preview of FIPS crypto packages undergoing certification with NIST - fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates - landscape +yes +n/a +Management and administration tool for Ubuntu - livepatch +yes +n/a +Canonical Livepatch service - realtime-kernel +yes +n/a +Ubuntu kernel with PREEMPT_RT patches integrated - ├ generic +yes +n/a +Generic version of the RT kernel \(default\) - └ intel-iotg +yes +n/a +RT kernel optimized for Intel IOTG platform - ros +yes +n/a +Security Updates for the Robot Operating System - ros-updates +yes +n/a +All Updates for the Robot Operating System - usg +yes +disabled +Security compliance and audit tools - - Enable services with: pro enable - """ - - Examples: ubuntu release - | release | machine_type | - | jammy | lxd-container | + @uses.config.contract_token + Scenario Outline: Attached status in a ubuntu machine - formatted + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `pro status --format json` as non-root + Then stdout is a json matching the `ua_status` schema + When I run `pro status --format yaml` as non-root + Then stdout is a yaml matching the `ua_status` schema + When I create the file `/var/lib/ubuntu-advantage/machine-token-overlay.json` with the following: + """ + { + "machineTokenInfo": { + "contractInfo": { + "effectiveTo": null + } + } + } + """ + And I append the following on uaclient config: + """ + features: + machine_token_overlay: "/var/lib/ubuntu-advantage/machine-token-overlay.json" + """ + And I run `pro status` with sudo + Then stdout contains substring: + """ + Valid until: Unknown/Expired + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + @uses.config.contract_token + Scenario Outline: Non-root status can see in-progress operations + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + When I run shell command `sudo pro enable cis >/dev/null & pro status` as non-root + Then stdout matches regexp: + """ + NOTICES + Operation in progress: pro enable + """ + When I run `pro status --wait` as non-root + When I run `pro disable cis --assume-yes` with sudo + When I run shell command `sudo pro enable cis & pro status --wait` as non-root + Then stdout matches regexp: + """ + One moment, checking your subscription first + Configuring APT access to CIS Audit + Updating CIS Audit package lists + Updating standard Ubuntu package lists + Installing CIS Audit packages + CIS Audit enabled + Visit https://ubuntu.com/security/cis to learn how to use CIS + \.+ + SERVICE +ENTITLED +STATUS +DESCRIPTION + """ + Then stdout does not match regexp: + """ + NOTICES + Operation in progress: pro enable + """ + When I run `pro disable cis --assume-yes` with sudo + When I apt install `jq` + When I run shell command `sudo pro enable cis >/dev/null & pro status --format json | jq -r .execution_status` as non-root + Then I will see the following on stdout: + """ + active + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + + Scenario Outline: Attached status in a ubuntu Pro machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + log_level: debug + """ + And I run `pro auto-attach` with sudo + When I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages + cis +yes +disabled +Security compliance and audit tools + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips +yes +disabled +NIST-certified FIPS crypto packages + fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates + livepatch +yes +enabled +Canonical Livepatch service + """ + When I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +n/a +Scalable Android in the cloud + cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages + cis +yes +disabled +Security compliance and audit tools + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips +yes +disabled +NIST-certified FIPS crypto packages + fips-preview +yes +n/a +Preview of FIPS crypto packages undergoing certification with NIST + fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates + landscape +yes +n/a +Management and administration tool for Ubuntu + livepatch +yes +enabled +Canonical Livepatch service + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | aws.pro | + | xenial | azure.pro | + + Scenario Outline: Attached status in a ubuntu Pro machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + log_level: debug + """ + And I run `pro auto-attach` with sudo + And I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages + cis +yes +disabled +Security compliance and audit tools + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + livepatch +yes +warning +Current kernel is not supported + """ + When I verify root and non-root `pro status --all` calls have the same output + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +n/a +Scalable Android in the cloud + cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages + cis +yes +disabled +Security compliance and audit tools + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips +yes +n/a +NIST-certified FIPS crypto packages + fips-preview +yes +n/a +Preview of FIPS crypto packages undergoing certification with NIST + fips-updates +yes +n/a +FIPS compliant crypto packages with stable security updates + landscape +yes +n/a +Management and administration tool for Ubuntu + livepatch +yes +warning +Current kernel is not supported + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | gcp.pro | + + Scenario Outline: Attached status in a ubuntu Pro machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + log_level: debug + """ + And I run `pro auto-attach` with sudo + And I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages + cis +yes +disabled +Security compliance and audit tools + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips +yes +disabled +NIST-certified FIPS crypto packages + fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates + livepatch +yes +enabled +Canonical Livepatch service + """ + When I verify root and non-root `pro status --all` calls have the same output + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +n/a +Scalable Android in the cloud + cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages + cis +yes +disabled +Security compliance and audit tools + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips +yes +disabled +NIST-certified FIPS crypto packages + fips-preview +yes +n/a +Preview of FIPS crypto packages undergoing certification with NIST + fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates + landscape +yes +n/a +Management and administration tool for Ubuntu + livepatch +yes +enabled +Canonical Livepatch service + """ + + Examples: ubuntu release + | release | machine_type | + | bionic | aws.pro | + | bionic | azure.pro | + | bionic | gcp.pro | + + Scenario Outline: Attached status in a ubuntu Pro machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + log_level: debug + """ + And I run `pro auto-attach` with sudo + And I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +disabled +Scalable Android in the cloud + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips +yes +disabled +NIST-certified FIPS crypto packages + fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates + livepatch +yes +enabled +Canonical Livepatch service + usg +yes +disabled +Security compliance and audit tools + """ + When I verify root and non-root `pro status --all` calls have the same output + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +disabled +Scalable Android in the cloud + cc-eal +yes +n/a +Common Criteria EAL2 Provisioning Packages + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips +yes +disabled +NIST-certified FIPS crypto packages + fips-preview +yes +n/a +Preview of FIPS crypto packages undergoing certification with NIST + fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates + landscape +yes +n/a +Management and administration tool for Ubuntu + livepatch +yes +enabled +Canonical Livepatch service + usg +yes +disabled +Security compliance and audit tools + """ + + Examples: ubuntu release + | release | machine_type | + | focal | aws.pro | + | focal | azure.pro | + | focal | gcp.pro | + + Scenario Outline: Attached status in a ubuntu Pro machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + log_level: debug + """ + And I run `pro auto-attach` with sudo + And I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +disabled +Scalable Android in the cloud + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips-preview +yes +disabled +Preview of FIPS crypto packages undergoing certification with NIST + fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates + livepatch +yes +enabled +Canonical Livepatch service + usg +yes +disabled +Security compliance and audit tools + """ + When I verify root and non-root `pro status --all` calls have the same output + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +disabled +Scalable Android in the cloud + cc-eal +yes +n/a +Common Criteria EAL2 Provisioning Packages + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips +yes +n/a +NIST-certified FIPS crypto packages + fips-preview +yes +disabled +Preview of FIPS crypto packages undergoing certification with NIST + fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates + landscape +yes +n/a +Management and administration tool for Ubuntu + livepatch +yes +enabled +Canonical Livepatch service + usg +yes +disabled +Security compliance and audit tools + """ + + Examples: ubuntu release + | release | machine_type | + | jammy | aws.pro | + | jammy | azure.pro | + | jammy | gcp.pro | + + @uses.config.contract_token + Scenario Outline: Attached status in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages + cis +yes +disabled +Security compliance and audit tools + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips +yes +disabled +NIST-certified FIPS crypto packages + fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates + ros +yes +disabled +Security Updates for the Robot Operating System + ros-updates +yes +disabled +All Updates for the Robot Operating System + + For a list of all Ubuntu Pro services, run 'pro status --all' + Enable services with: pro enable + """ + When I verify root and non-root `pro status --all` calls have the same output + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +n/a +.* + cc-eal +yes +disabled +Common Criteria EAL2 Provisioning Packages + cis +yes +disabled +Security compliance and audit tools + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips +yes +disabled +NIST-certified FIPS crypto packages + fips-preview +yes +n/a +Preview of FIPS crypto packages undergoing certification with NIST + fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates + landscape +yes +n/a +Management and administration tool for Ubuntu + livepatch +yes +n/a +Canonical Livepatch service + realtime-kernel +yes +n/a +Ubuntu kernel with PREEMPT_RT patches integrated + ros +yes +disabled +Security Updates for the Robot Operating System + ros-updates +yes +disabled +All Updates for the Robot Operating System + + Enable services with: pro enable + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | bionic | wsl | + + @uses.config.contract_token + Scenario Outline: Attached status in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +disabled +.* + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips +yes +disabled +NIST-certified FIPS crypto packages + fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates + ros +yes +disabled +Security Updates for the Robot Operating System + usg +yes +disabled +Security compliance and audit tools + + For a list of all Ubuntu Pro services, run 'pro status --all' + Enable services with: pro enable + """ + When I verify root and non-root `pro status --all` calls have the same output + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +disabled +.* + cc-eal +yes +n/a +Common Criteria EAL2 Provisioning Packages + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips +yes +disabled +NIST-certified FIPS crypto packages + fips-preview +yes +n/a +Preview of FIPS crypto packages undergoing certification with NIST + fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates + landscape +yes +n/a +Management and administration tool for Ubuntu + livepatch +yes +n/a +Canonical Livepatch service + realtime-kernel +yes +n/a +Ubuntu kernel with PREEMPT_RT patches integrated + ros +yes +disabled +Security Updates for the Robot Operating System + ros-updates +yes +n/a +All Updates for the Robot Operating System + usg +yes +disabled +Security compliance and audit tools + + Enable services with: pro enable + """ + + Examples: ubuntu release + | release | machine_type | + | focal | lxd-container | + | focal | wsl | + + @uses.config.contract_token + Scenario Outline: Attached status in the latest LTS ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +disabled +.* + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips-preview +yes +disabled +Preview of FIPS crypto packages undergoing certification with NIST + fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates + usg +yes +disabled +Security compliance and audit tools + + For a list of all Ubuntu Pro services, run 'pro status --all' + Enable services with: pro enable + """ + When I verify root and non-root `pro status --all` calls have the same output + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +disabled +.* + cc-eal +yes +n/a +Common Criteria EAL2 Provisioning Packages + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips +yes +n/a +NIST-certified FIPS crypto packages + fips-preview +yes +disabled +Preview of FIPS crypto packages undergoing certification with NIST + fips-updates +yes +disabled +FIPS compliant crypto packages with stable security updates + landscape +yes +n/a +Management and administration tool for Ubuntu + livepatch +yes +n/a +Canonical Livepatch service + realtime-kernel +yes +n/a +Ubuntu kernel with PREEMPT_RT patches integrated + ├ generic +yes +n/a +Generic version of the RT kernel \(default\) + └ intel-iotg +yes +n/a +RT kernel optimized for Intel IOTG platform + ros +yes +n/a +Security Updates for the Robot Operating System + ros-updates +yes +n/a +All Updates for the Robot Operating System + usg +yes +disabled +Security compliance and audit tools + + Enable services with: pro enable + """ + + Examples: ubuntu release + | release | machine_type | + | jammy | lxd-container | + + @uses.config.contract_token + Scenario Outline: Attached status in the latest LTS ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +disabled +.* + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + landscape +yes +disabled +Management and administration tool for Ubuntu + + For a list of all Ubuntu Pro services, run 'pro status --all' + Enable services with: pro enable + """ + When I verify root and non-root `pro status --all` calls have the same output + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +ENTITLED +STATUS +DESCRIPTION + anbox-cloud +yes +disabled +.* + cc-eal +yes +n/a +Common Criteria EAL2 Provisioning Packages + esm-apps +yes +enabled +Expanded Security Maintenance for Applications + esm-infra +yes +enabled +Expanded Security Maintenance for Infrastructure + fips +yes +n/a +NIST-certified FIPS crypto packages + fips-preview +yes +n/a +Preview of FIPS crypto packages undergoing certification with NIST + fips-updates +yes +n/a +FIPS compliant crypto packages with stable security updates + landscape +yes +disabled +Management and administration tool for Ubuntu + livepatch +yes +n/a +Canonical Livepatch service + realtime-kernel +yes +n/a +Ubuntu kernel with PREEMPT_RT patches integrated + ros +yes +n/a +Security Updates for the Robot Operating System + ros-updates +yes +n/a +All Updates for the Robot Operating System + usg +yes +n/a +Security compliance and audit tools + + Enable services with: pro enable + """ + + Examples: ubuntu release + | release | machine_type | + | noble | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/cloud.py ubuntu-advantage-tools-32~16.04/features/cloud.py --- ubuntu-advantage-tools-31.2.3~16.04/features/cloud.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/cloud.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,11 +1,17 @@ import json import logging import os +import shlex +import time +from contextlib import suppress from typing import List, Optional import pycloudlib # type: ignore import toml +from paramiko.ssh_exception import NoValidConnectionsError, SSHException from pycloudlib.cloud import ImageType # type: ignore +from pycloudlib.errors import PycloudlibTimeoutError # type: ignore +from pycloudlib.result import Result # type: ignore DEFAULT_CONFIG_PATH = "~/.config/pycloudlib.toml" @@ -37,6 +43,13 @@ return LXDContainer( cloud_credentials_path=pro_config.cloud_credentials_path, ) + if cloud_name == "wsl": + return WSL( + wsl_pubkey_path=pro_config.wsl_pubkey_path, + wsl_privkey_path=pro_config.wsl_privkey_path, + wsl_ip_address=pro_config.wsl_ip_address, + cloud_credentials_path=pro_config.cloud_credentials_path, + ) raise RuntimeError("Invalid cloud name") @@ -52,6 +65,9 @@ self.clouds[cloud_name] = cloud return cloud + def has(self, cloud_name: str): + return cloud_name in self.clouds + class Cloud: """Base class for cloud providers that should be tested through behave. @@ -763,3 +779,488 @@ def pycloudlib_cls(self): """Return the pycloudlib cls to be used as an api.""" return pycloudlib.LXDContainer + + +class WSLCloud(pycloudlib.cloud.BaseCloud): + def __init__( + self, + wsl_pubkey_path: str, + wsl_privkey_path: str, + wsl_ip_address: str, + ): + self.wsl_pubkey_path = wsl_pubkey_path + self.wsl_privkey_path = wsl_privkey_path + self.wsl_ip_address = wsl_ip_address + super().__init__(tag="wsl") + + def _check_and_set_config(self, config_file, required_values): + self.config = { + "public_key_path": self.wsl_pubkey_path, + "private_key_path": self.wsl_privkey_path, + } + + def daily_image(self, release, **kwargs): + return self.released_images(release, **kwargs) + + def delete_image(self, image_name): + raise NotImplementedError + + def get_instance(self, instance_name): + raise NotImplementedError + + def image_serial(self, release): + raise NotImplementedError + + def launch(self, series: str): + instance_parameters = self.released_image(series) + inst = WSLInstance( + self.key_pair, + series=series, + ip_address=self.wsl_ip_address, + ) + inst._wait_for_execute() + inst.delete() + inst.uninstall_ubuntu_installer(instance_parameters["appxname"]) + + inst.install_ubuntu_distro( + store_id=instance_parameters["store_id"], + ) + inst.launch_ubuntu_distro( + launcher_name=instance_parameters["launcher"], + ) + inst.create_non_root_user() + + return inst + + def snapshot(self): + raise NotImplementedError + + def released_image(self, release: str, **kwargs): + wsl_releases = { + "jammy": { + "name": "Ubuntu-22.04", + "store_id": "9PN20MSR04DW", + "launcher": "ubuntu2204.exe", + "appxname": "Ubuntu22.04LTS", + }, + "focal": { + "name": "Ubuntu-20.04", + "store_id": "9MTTCL66CPXJ", + "launcher": "ubuntu2004.exe", + "appxname": "Ubuntu20.04LTS", + }, + "bionic": { + "name": "Ubuntu-18.04", + "store_id": "9PNKSF5ZN4SW", + "launcher": "ubuntu1804.exe", + "appxname": "Ubuntu18.04LTS", + }, + } + + return wsl_releases.get(release) + + +class WSLInstance(pycloudlib.instance.BaseInstance): + WSL_UBUNTU_MAP = { + "jammy": "Ubuntu-22.04", + "focal": "Ubuntu-20.04", + "bionic": "Ubuntu-18.04", + } + + def __init__( + self, + key_pair, + series: str, + ip_address: str, + ): + super().__init__(key_pair) + self.ip_address = ip_address + self.series = self.WSL_UBUNTU_MAP.get(series, "None") + + def install_ubuntu_distro(self, store_id: str): + install_cmd = ( + 'winget install --id "{}" --accept-source-agreements ' + "--accept-package-agreements --silent" + ) + self.execute( + install_cmd.format(store_id), + run_on_wsl=False, + check_stderr=True, + ) + + def launch_ubuntu_distro(self, launcher_name: str): + self.execute( + "{} install --root --ui=none".format(launcher_name), + run_on_wsl=False, + check_stderr=True, + ) + + def create_non_root_user(self, username="ubuntu"): + self.execute( + 'sh -c "sudo adduser --disabled-password --gecos test {}"'.format( + username + ), + run_on_wsl=True, + use_sudo=True, + check_stderr=True, + ) + + self.execute( + "passwd -d {}".format(username), + run_on_wsl=True, + check_stderr=True, + use_sudo=True, + ) + + configure_cmd = "sh -c \"echo '{} ALL=(ALL:ALL) NOPASSWD:ALL' | sudo EDITOR='tee -a' visudo\"" # noqa + self.execute( + configure_cmd.format(username), + run_on_wsl=True, + use_sudo=True, + check_stderr=True, + ) + + def execute( + self, + command, + stdin=None, + run_on_wsl=True, + use_sudo=False, + check_stderr=False, + **kwargs + ): + if isinstance(command, list): + command = shlex.join(command) + + if isinstance(command, str): + if run_on_wsl: + script_path = "/tmp/wsl-bash-script.sh" + with open(script_path, "w") as f: + f.write(command) + + self.push_file( + local_path=script_path, + remote_path=script_path, + ) + + user = "root" if use_sudo else "ubuntu" + script_cmd = "bash {}".format(script_path) + wsl_cmd = "wsl -d {} -u {} --exec {}" + command = wsl_cmd.format( + self.series, + user, + script_cmd, + ) + + ret = self._ssh(command, stdin) + + if ret.stderr and check_stderr: + logging.info("--- Error running cmd:\n{}".format(command)) + logging.info(ret.stderr) + raise Exception("Command failed") + + return ret + + def _ssh(self, cmd, stdin=None): + """Run a command via SSH. + + Args: + command: string or list of the command to run + stdin: optional, values to be passed in + + Returns: + tuple of stdout, stderr and the return code + + """ + # I need to redefine this method here just to avoid calling + # shell_pack, as it won't work on Windows or WSL instances + client = self._ssh_connect() + try: + fp_in, fp_out, fp_err = client.exec_command(cmd) + except (ConnectionResetError, NoValidConnectionsError, EOFError) as e: + raise SSHException from e + channel = fp_in.channel + + if stdin is not None: + fp_in.write(stdin) + fp_in.close() + + channel.shutdown_write() + + out = fp_out.read() + err = fp_err.read() + return_code = channel.recv_exit_status() + + out = "" if not out else out.rstrip().decode("utf-8") + err = "" if not err else err.rstrip().decode("utf-8") + + return Result(out, err, return_code) + + def delete(self, wait=False): + self.execute("wsl --shutdown", run_on_wsl=False, check_stderr=True) + self.execute( + "wsl --unregister {}".format(self.series), + run_on_wsl=False, + check_stderr=True, + ) + + def push_file(self, local_path, remote_path): + windows_local_path = r"C:\Users\ubuntu\AppData\Local\Temp\pro_file" + super().push_file(local_path, windows_local_path) + + wsl_cmd = "wsl -d {} -u root --exec {}".format( + self.series, + "mv /mnt/c/Users/ubuntu/AppData/Local/Temp/pro_file {}".format( + remote_path + ), + ) + + self.execute( + wsl_cmd, + run_on_wsl=False, + ) + + def uninstall_ubuntu_installer(self, appx_name: str): + distro_installer_cmd = ( + 'powershell -Command "(Get-AppxPackage | Where-Object Name -like' + " 'CanonicalGroupLimited.{}').PackageFullName\"" + ) + distro_installer_path = self.execute( + distro_installer_cmd.format(appx_name), + run_on_wsl=False, + check_stderr=True, + ) + + if distro_installer_path.stdout: + distro_installer_remove_cmd = ( + 'powershell -Command "Remove-AppxPackage (Get-AppxPackage | Where-Object Name -like' # noqa + " 'CanonicalGroupLimited.{}').PackageFullName\"" + ) + self.execute( + distro_installer_remove_cmd.format(appx_name), + run_on_wsl=False, + check_stderr=True, + ) + + @property + def ip(self): + return self.ip_address + + def id(self): + return "wsl" + + def _do_restart(self): + pass + + @property + def name(self): + return self.series + + def shutdown(self): + self.execute( + "wsl -d {} --shutdown".format(self.series), + run_on_wsl=False, + check_stderr=True, + ) + + def start(self): + pass + + def wait_for_delete(self): + pass + + def wait_for_stop(self): + pass + + def _wait_for_execute(self): + # Wait 40 minutes before failing. AWS EC2 metal instances can take + # over 20 minutes to start or restart, so we shouldn't lower + # this timeout + start = time.time() + end = start + 40 * 60 + while time.time() < end: + with suppress(SSHException, OSError): + ret = self.execute("dir", run_on_wsl=False) + if not ret.failed: + return + time.sleep(1) + + raise PycloudlibTimeoutError( + "Instance can't be reached after 40 minutes. " + "Failed to obtain new boot id", + ) + + +class WSL(Cloud): + name = "" + + def __init__( + self, + wsl_pubkey_path: str, + wsl_privkey_path: str, + wsl_ip_address: str, + cloud_credentials_path: Optional[str], + ) -> None: + self._api = None + self._azure_api = None + self.wsl_pubkey_path = wsl_pubkey_path + self.wsl_privkey_path = wsl_privkey_path + self.wsl_ip_address = wsl_ip_address + self.cloud_credentials_path = cloud_credentials_path + self._ssh_key_managed = False + + @property + def pycloudlib_cls(self): + """Return the pycloudlib cls to be used as an api.""" + return WSLCloud + + @property + def azure_api(self): + if self._azure_api is None: + self._azure_api = pycloudlib.Azure( + config_file=self.cloud_credentials_path, + tag="wsl", + ) + + return self._azure_api + + @property + def api(self) -> pycloudlib.cloud.BaseCloud: + """Return the api used to interact with the cloud provider.""" + if self._api is None: + self._api = self.pycloudlib_cls( + wsl_pubkey_path=self.wsl_pubkey_path, + wsl_privkey_path=self.wsl_privkey_path, + wsl_ip_address=self.wsl_ip_address, + ) + + return self._api + + def stop_windows_machine(self): + windows_machine = self.azure_api.get_instance( + instance_id="wsl-test", + search_all=True, + ) + + windows_machine.shutdown(wait=True) + + def start_windows_machine(self): + windows_machine = self.azure_api.get_instance( + instance_id="wsl-test", + search_all=True, + ) + + start = windows_machine._client.virtual_machines.begin_start( + resource_group_name=windows_machine._instance["rg_name"], + vm_name=windows_machine.name, + ) + start.wait() + + def _create_instance( + self, + series: str, + machine_type: str, + instance_name: Optional[str] = None, + image_name: Optional[str] = None, + user_data: Optional[str] = None, + ephemeral: bool = False, + inbound_ports: Optional[List[str]] = None, + ) -> pycloudlib.instance: + """Create an instance for on the cloud provider. + + :param series: + The ubuntu release to be used when creating an instance. We will + create an image based on this value if the user does not provide + a image_name value + + :returns: + A cloud provider instance + """ + return self.api.launch(series) + + def launch( + self, + series: str, + machine_type: str, + instance_name: Optional[str] = None, + image_name: Optional[str] = None, + user_data: Optional[str] = None, + ephemeral: bool = False, + inbound_ports: Optional[List[str]] = None, + ) -> pycloudlib.instance.BaseInstance: + """Create and wait for cloud provider instance to be ready. + + :param series: + The ubuntu release to be used when creating an instance. We will + create an image based on this value if the used does not provide + a image_name value + + :returns: + An cloud provider instance + """ + + self.start_windows_machine() + + inst = self._create_instance( + series=series, + machine_type=machine_type, + ) + logging.info("--- WSL {} instance launched.".format(inst.name)) + + return inst + + def get_instance_id( + self, instance: pycloudlib.instance.BaseInstance + ) -> str: + """Return the instance identifier. + + :param instance: + An instance created on the cloud provider + + :returns: + The string of the unique instance id + """ + return instance.id + + def locate_image_name( + self, + series: str, + machine_type: str, + daily: bool = True, + include_deprecated: bool = False, + ) -> str: + """Locate and return the WSL image name to use for vm provision. + + :param series: + The ubuntu release to be used when locating the image name + + :returns: + A image name to use when provisioning a virtual machine + based on the series value + """ + if not series: + raise ValueError( + "Must provide either series or image_name to launch azure" + ) + + return self.api.released_image(release=series) + + def manage_ssh_key( + self, + private_key_path: Optional[str] = None, + key_name: Optional[str] = None, + ) -> None: + """Create and manage ssh key pairs to be used in the cloud provider. + + :param private_key_path: + Location of the private key path to use. If None, the location + will be a default location. + """ + if self._ssh_key_managed: + logging.debug("SSH key already set up") + return + + self.api.use_key( + public_key_path=self.wsl_pubkey_path, + private_key_path=self.wsl_privkey_path, + ) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/cloud_pro_clone.feature ubuntu-advantage-tools-32~16.04/features/cloud_pro_clone.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/cloud_pro_clone.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/cloud_pro_clone.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,56 +1,56 @@ Feature: Creating golden images based on Cloud Ubuntu Pro instances - Scenario Outline: Create a Pro fips-updates image and launch - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - data_dir: /var/lib/ubuntu-advantage - log_level: debug - log_file: /var/log/ubuntu-advantage.log - """ - When I run `pro auto-attach` with sudo - Then the machine is attached - When I apt update - When I apt install `jq` - When I save the `activityInfo.activityToken` value from the contract - When I save the `activityInfo.activityID` value from the contract - When I run `pro enable fips-updates --assume-yes` with sudo - And I run `pro status --format yaml` with sudo - Then stdout matches regexp: - """ - name: fips-updates - status: enabled - """ - When I reboot the machine - When I take a snapshot of the machine - When I reboot the machine - When I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo - Then I verify that `activityInfo.activityToken` value has been updated on the contract - Then I verify that `activityInfo.activityID` value has not been updated on the contract - When I launch a `` `` machine named `clone` from the snapshot of `system-under-test` - # The clone will run auto-attach on boot - When I run `pro status --wait` `with sudo` on the `clone` machine - Then the machine is attached - When I run `python3 /usr/lib/ubuntu-advantage/timer.py` `with sudo` on the `clone` machine - Then I verify that `activityInfo.activityToken` value has been updated on the contract on the `clone` machine - Then I verify that `activityInfo.activityID` value has been updated on the contract on the `clone` machine - When I run `pro status --format yaml` `with sudo` on the `clone` machine - Then stdout matches regexp: - """ - name: fips-updates - status: enabled - """ - When I reboot the `clone` machine - When I run `pro status --format yaml` `with sudo` on the `clone` machine - Then stdout matches regexp: - """ - name: fips-updates - status: enabled - """ - Examples: ubuntu release - | release | machine_type | - | bionic | aws.pro | - | bionic | gcp.pro | - | focal | aws.pro | - | focal | gcp.pro | + Scenario Outline: Create a Pro fips-updates image and launch + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + data_dir: /var/lib/ubuntu-advantage + log_level: debug + log_file: /var/log/ubuntu-advantage.log + """ + When I run `pro auto-attach` with sudo + Then the machine is attached + When I apt install `jq` + When I save the `activityInfo.activityToken` value from the contract + When I save the `activityInfo.activityID` value from the contract + When I run `pro enable fips-updates --assume-yes` with sudo + And I run `pro status --format yaml` with sudo + Then stdout matches regexp: + """ + \s*name: fips-updates + \s*status: enabled + """ + When I reboot the machine + When I take a snapshot of the machine + When I reboot the machine + When I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo + Then I verify that `activityInfo.activityToken` value has been updated on the contract + Then I verify that `activityInfo.activityID` value has not been updated on the contract + When I launch a `` `` machine named `clone` from the snapshot of `system-under-test` + # The clone will run auto-attach on boot + When I run `pro status --wait` `with sudo` on the `clone` machine + Then the machine is attached + When I run `python3 /usr/lib/ubuntu-advantage/timer.py` `with sudo` on the `clone` machine + Then I verify that `activityInfo.activityToken` value has been updated on the contract on the `clone` machine + Then I verify that `activityInfo.activityID` value has been updated on the contract on the `clone` machine + When I run `pro status --format yaml` `with sudo` on the `clone` machine + Then stdout matches regexp: + """ + \s*name: fips-updates + \s*status: enabled + """ + When I reboot the `clone` machine + When I run `pro status --format yaml` `with sudo` on the `clone` machine + Then stdout matches regexp: + """ + \s*name: fips-updates + \s*status: enabled + """ + + Examples: ubuntu release + | release | machine_type | + | bionic | aws.pro | + | bionic | gcp.pro | + | focal | aws.pro | + | focal | gcp.pro | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/collect_logs.feature ubuntu-advantage-tools-32~16.04/features/collect_logs.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/collect_logs.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/collect_logs.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,92 +1,98 @@ Feature: Command behaviour when attached to an Ubuntu Pro subscription - Scenario Outline: Run collect-logs on an unattached machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo - # simulate logrotate - When I run `touch /var/log/ubuntu-advantage.log.1` with sudo - When I run `touch /var/log/ubuntu-advantage.log.2.gz` with sudo - When I run `pro collect-logs` as non-root - Then I verify that files exist matching `ua_logs.tar.gz` - When I run `tar zxf ua_logs.tar.gz` with sudo - Then I verify that files exist matching `logs/` - When I run `sh -c "ls -1 logs/ | sort -d"` as non-root - # On Xenial, the return value for inexistent services is the same as for dead ones (3). - # So the -error suffix does not appear there. - Then stdout matches regexp: - """ - apt-news.service.txt - build.info - cloud-id.txt - cloud-init-journal.txt - esm-cache.service.txt - jobs-status.json - livepatch-status.txt-error - pro-journal.txt - systemd-timers.txt - ua-auto-attach.path.txt(-error)? - ua-auto-attach.service.txt(-error)? - uaclient.conf - ua-reboot-cmds.service.txt - ua-status.json - ua-timer.service.txt - ua-timer.timer.txt - ubuntu-advantage.log - ubuntu-advantage.log.1 - ubuntu-advantage.log.2.gz - ubuntu-advantage.service.txt - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | + Scenario Outline: Run collect-logs on an unattached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo + # simulate logrotate + When I run `touch /var/log/ubuntu-advantage.log.1` with sudo + When I run `touch /var/log/ubuntu-advantage.log.2.gz` with sudo + When I run `pro collect-logs` as non-root + Then I verify that files exist matching `pro_logs.tar.gz` + When I run `tar zxf pro_logs.tar.gz` with sudo + Then I verify that files exist matching `logs/` + When I run `sh -c "ls -1 logs/ | sort -d"` as non-root + # On Xenial, the return value for inexistent services is the same as for dead ones (3). + # So the -error suffix does not appear there. + Then stdout matches regexp: + """ + apt-news.service.txt + build.info + cloud-id.txt + cloud-init-journal.txt + environment_vars.json + esm-cache.service.txt + jobs-status.json + livepatch-status.txt-error + pro-journal.txt + pro-status.json + systemd-timers.txt + ua-auto-attach.path.txt(-error)? + ua-auto-attach.service.txt(-error)? + uaclient.conf + ua-reboot-cmds.service.txt + ua-timer.service.txt + ua-timer.timer.txt + ubuntu-advantage.log + ubuntu-advantage.log.1 + ubuntu-advantage.log.2.gz + ubuntu-advantage.service.txt + """ - @uses.config.contract_token - Scenario Outline: Run collect-logs on an attached machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo - # simulate logrotate - When I run `touch /var/log/ubuntu-advantage.log.1` with sudo - When I run `touch /var/log/ubuntu-advantage.log.2.gz` with sudo - When I run `pro collect-logs` as non-root - Then I verify that files exist matching `ua_logs.tar.gz` - When I run `tar zxf ua_logs.tar.gz` as non-root - Then I verify that files exist matching `logs/` - When I run `sh -c "ls -1 logs/ | sort -d"` as non-root - # On Xenial, the return value for inexistent services is the same as for dead ones (3). - # So the -error suffix does not appear there. - Then stdout matches regexp: - """ - apt-news.service.txt - build.info - cloud-id.txt - cloud-init-journal.txt - esm-cache.service.txt - jobs-status.json - livepatch-status.txt-error - pro-journal.txt - systemd-timers.txt - ua-auto-attach.path.txt(-error)? - ua-auto-attach.service.txt(-error)? - uaclient.conf - ua-reboot-cmds.service.txt - ua-status.json - ua-timer.service.txt - ua-timer.timer.txt - ubuntu-advantage.log - ubuntu-advantage.log.1 - ubuntu-advantage.log.2.gz - ubuntu-advantage.service.txt - ubuntu-esm-apps.list - ubuntu-esm-infra.list - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + @uses.config.contract_token + Scenario Outline: Run collect-logs on an attached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `python3 /usr/lib/ubuntu-advantage/timer.py` with sudo + # simulate logrotate + When I run `touch /var/log/ubuntu-advantage.log.1` with sudo + When I run `touch /var/log/ubuntu-advantage.log.2.gz` with sudo + When I run `pro collect-logs` as non-root + Then I verify that files exist matching `pro_logs.tar.gz` + When I run `tar zxf pro_logs.tar.gz` as non-root + Then I verify that files exist matching `logs/` + When I run `sh -c "ls -1 logs/ | sort -d"` as non-root + # On Xenial, the return value for inexistent services is the same as for dead ones (3). + # So the -error suffix does not appear there. + Then stdout matches regexp: + """ + apt-news.service.txt + build.info + cloud-id.txt + cloud-init-journal.txt + environment_vars.json + esm-cache.service.txt + jobs-status.json + livepatch-status.txt-error + pro-journal.txt + pro-status.json + systemd-timers.txt + ua-auto-attach.path.txt(-error)? + ua-auto-attach.service.txt(-error)? + uaclient.conf + ua-reboot-cmds.service.txt + ua-timer.service.txt + ua-timer.timer.txt + ubuntu-advantage.log + ubuntu-advantage.log.1 + ubuntu-advantage.log.2.gz + ubuntu-advantage.service.txt + ubuntu-esm-apps.(list|sources) + ubuntu-esm-infra.(list|sources) + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/config/protest.example.yaml ubuntu-advantage-tools-32~16.04/features/config/protest.example.yaml --- ubuntu-advantage-tools-31.2.3~16.04/features/config/protest.example.yaml 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/config/protest.example.yaml 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,15 @@ +contract_token: +contract_token_staging: +contract_token_staging_expired: +contract_token_staging_expired_sometimes: + +contract_staging_service_account_username: pro-client-ci +contract_staging_service_account_password: + +landscape_registration_key: +landscape_api_access_key: +landscape_api_secret_key: + +wsl_pubkey_path: +wsl_privkey_path: +wsl_ip_address: diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/config.feature ubuntu-advantage-tools-32~16.04/features/config.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/config.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/config.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,52 +1,54 @@ Feature: pro config sub-command - # earliest, latest lts[, latest stable] - Scenario Outline: old ua_config in uaclient.conf is still supported - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro config show` with sudo - Then I will see the following on stdout: - """ - http_proxy None - https_proxy None - apt_http_proxy None - apt_https_proxy None - ua_apt_http_proxy None - ua_apt_https_proxy None - global_apt_http_proxy None - global_apt_https_proxy None - update_messaging_timer 21600 - metering_timer 14400 - apt_news True - apt_news_url https://motd.ubuntu.com/aptnews.json - """ - Then I will see the following on stderr: - """ - """ - When I append the following on uaclient config: - """ - ua_config: {apt_news: false} - """ - When I run `pro config show` with sudo - Then I will see the following on stdout: - """ - http_proxy None - https_proxy None - apt_http_proxy None - apt_https_proxy None - ua_apt_http_proxy None - ua_apt_https_proxy None - global_apt_http_proxy None - global_apt_https_proxy None - update_messaging_timer 21600 - metering_timer 14400 - apt_news False - apt_news_url https://motd.ubuntu.com/aptnews.json - """ - Then I will see the following on stderr: - """ - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | + # earliest, latest lts[, latest stable] + Scenario Outline: old ua_config in uaclient.conf is still supported + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro config show` with sudo + Then I will see the following on stdout: + """ + http_proxy None + https_proxy None + apt_http_proxy None + apt_https_proxy None + ua_apt_http_proxy None + ua_apt_https_proxy None + global_apt_http_proxy None + global_apt_https_proxy None + update_messaging_timer 21600 + metering_timer 14400 + apt_news True + apt_news_url https://motd.ubuntu.com/aptnews.json + """ + Then I will see the following on stderr: + """ + """ + When I append the following on uaclient config: + """ + ua_config: {apt_news: false} + """ + When I run `pro config show` with sudo + Then I will see the following on stdout: + """ + http_proxy None + https_proxy None + apt_http_proxy None + apt_https_proxy None + ua_apt_http_proxy None + ua_apt_https_proxy None + global_apt_http_proxy None + global_apt_https_proxy None + update_messaging_timer 21600 + metering_timer 14400 + apt_news False + apt_news_url https://motd.ubuntu.com/aptnews.json + """ + Then I will see the following on stderr: + """ + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/contract_expired.feature ubuntu-advantage-tools-32~16.04/features/contract_expired.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/contract_expired.feature 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/contract_expired.feature 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,63 @@ +Feature: End of contract messages + + @vpn @uses.config.contract_token + Scenario Outline: Display expired messages in all relevant places + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt upgrade + When I apt install `hello update-motd` + When I set the test contract expiration date to `$behave_var{today +1}` + When I attach `contract_token_staging_expired_sometimes` with sudo and options `--no-auto-enable` + # HACK NOTICE: This part relies on implementation details of pro-client + # we hack apt-helper to let pro enable go through to simulate being expired with services enabled + # we can't just enable before expiring the contract because esm-auth is cached + When I create the file `/usr/bin/apt-helper-always-true` with the following + """ + #!/usr/bin/bash + true + """ + When I run `chmod +x /usr/bin/apt-helper-always-true` with sudo + When I run `mv /usr/lib/apt/apt-helper /usr/lib/apt/apt-helper.backup` with sudo + When I run `ln -s /usr/bin/apt-helper-always-true /usr/lib/apt/apt-helper` with sudo + When I run `pro enable esm-apps` with sudo + When I set the test contract expiration date to `$behave_var{today -20}` + When I run `pro refresh` with sudo + When I run `pro status` with sudo + Then stdout contains substring: + """ + *Your Ubuntu Pro subscription has EXPIRED* + Renew your subscription at https://ubuntu.com/pro/dashboard + """ + When I verify that running `apt upgrade -y` `with sudo` exits `100` + Then stdout contains substring: + """ + # + # *Your Ubuntu Pro subscription has EXPIRED* + # Renew your subscription at https://ubuntu.com/pro/dashboard + # + The following packages will fail to download because your Ubuntu Pro subscription has expired + hello + Renew your subscription or `sudo pro detach` to remove these errors + """ + And stderr matches regexp: + """ + E: Failed to fetch https://esm\.staging\.ubuntu\.com/apps/ubuntu/pool/main/h/hello/hello_(.*)\.deb 401 Unauthorized \[IP: (.*) 443\] + """ + When I run `update-motd` with sudo + Then stdout contains substring: + """ + *Your Ubuntu Pro subscription has EXPIRED* + 1 additional security update requires Ubuntu Pro with 'esm-apps' enabled. + Renew your subscription at https://ubuntu.com/pro/dashboard + """ + When I run `pro disable esm-apps` with sudo + When I verify that running `pro enable esm-apps` `with sudo` exits `1` + Then I will see the following on stdout: + """ + One moment, checking your subscription first + *Your Ubuntu Pro subscription has EXPIRED* + Renew your subscription at https://ubuntu.com/pro/dashboard + """ + + Examples: ubuntu release + | release | machine_type | + | jammy | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/daemon.feature ubuntu-advantage-tools-32~16.04/features/daemon.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/daemon.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/daemon.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,445 +1,452 @@ Feature: Pro Upgrade Daemon only runs in environments where necessary - @uses.config.contract_token - Scenario Outline: cloud-id-shim service is not installed on anything other than xenial - Given a `` `` machine with ubuntu-advantage-tools installed - Then I verify that running `systemctl status ubuntu-advantage-cloud-id-shim.service` `with sudo` exits `4` - Then stderr matches regexp: - """ - Unit ubuntu-advantage-cloud-id-shim.service could not be found. - """ - Examples: version - | release | machine_type | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - @uses.config.contract_token - Scenario Outline: cloud-id-shim should run in postinst and on boot - Given a `` `` machine with ubuntu-advantage-tools installed - # verify installing pro created the cloud-id file - When I run `cat /run/cloud-init/cloud-id` with sudo - Then I will see the following on stdout - """ - lxd - """ - When I run `cat /run/cloud-init/cloud-id-lxd` with sudo - Then I will see the following on stdout - """ - lxd - """ - # verify the shim service runs on boot and creates the cloud-id file - When I reboot the machine - Then I verify that running `systemctl status ubuntu-advantage-cloud-id-shim.service` `with sudo` exits `3` - Then stdout matches regexp: - """ - (code=exited, status=0/SUCCESS) - """ - When I run `cat /run/cloud-init/cloud-id` with sudo - Then I will see the following on stdout - """ - lxd - """ - When I run `cat /run/cloud-init/cloud-id-lxd` with sudo - Then I will see the following on stdout - """ - lxd - """ - Examples: version - | release | machine_type | - | xenial | lxd-container | - - @uses.config.contract_token - Scenario Outline: daemon should run when appropriate on gcp generic lts - Given a `` `` machine with ubuntu-advantage-tools installed - # verify its enabled, but stops itself when not configured to poll - When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo - Then stdout contains substring: - """ - daemon starting - """ - Then stdout contains substring: - """ - Configured to not poll for pro license, shutting down - """ - Then stdout contains substring: - """ - daemon ending - """ - When I run `systemctl is-enabled ubuntu-advantage.service` with sudo - Then stdout matches regexp: - """ - enabled - """ - Then I verify that running `systemctl is-failed ubuntu-advantage.service` `with sudo` exits `1` - Then stdout matches regexp: - """ - inactive - """ - - # verify it stays on when configured to do so - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { "poll_for_pro_license": true } - """ - # Turn on memory accounting - When I run `sed -i s/#DefaultMemoryAccounting=no/DefaultMemoryAccounting=yes/ /etc/systemd/system.conf` with sudo - When I run `systemctl daemon-reexec` with sudo - - # on bionic, systemd version=237; which does not allow for log rotation + vacuum in same line e.g. - # journalctl --flush --rotate --vacuum-time=1s - When I run `journalctl --flush --rotate` with sudo - When I run `journalctl --vacuum-time=1s` with sudo - When I run `systemctl restart ubuntu-advantage.service` with sudo - - # wait to get memory after it has settled/after startup checks - When I wait `5` seconds - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `0` - Then stdout matches regexp: - """ - Active: active \(running\) - """ - # TODO find out what caused memory to go up, try to lower it again - Then on `xenial`, systemd status output says memory usage is less than `17` MB - Then on `bionic`, systemd status output says memory usage is less than `15` MB - Then on `focal`, systemd status output says memory usage is less than `14` MB - Then on `jammy`, systemd status output says memory usage is less than `14` MB - - When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo - Then stdout contains substring: - """ - daemon starting - """ - Then stdout does not contain substring: - """ - daemon ending - """ - When I run `systemctl is-enabled ubuntu-advantage.service` with sudo - Then stdout matches regexp: - """ - enabled - """ - Then I verify that running `systemctl is-failed ubuntu-advantage.service` `with sudo` exits `1` - Then stdout matches regexp: - """ - active - """ - - # verify attach stops it immediately and doesn't restart after reboot - When I attach `contract_token` with sudo - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` - Then stdout matches regexp: - """ - Active: inactive \(dead\) - """ - When I reboot the machine - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` - Then stdout matches regexp: - """ - Active: inactive \(dead\) - \s*Condition: start condition failed.* - .*ConditionPathExists=!/var/lib/ubuntu-advantage/private/machine-token.json was not met - """ - - # verify detach starts it and it starts again after reboot - When I run `journalctl --flush --rotate` with sudo - When I run `journalctl --vacuum-time=1s` with sudo - When I run `pro detach --assume-yes` with sudo - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `0` - Then stdout matches regexp: - """ - Active: active \(running\) - """ - When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo - Then stdout contains substring: - """ - daemon starting - """ - Then stdout does not contain substring: - """ - daemon ending - """ - When I reboot the machine - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `0` - Then stdout matches regexp: - """ - Active: active \(running\) - """ - When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo - Then stdout contains substring: - """ - daemon starting - """ - Then stdout does not contain substring: - """ - daemon ending - """ - - # Verify manual stop & disable persists across reconfigure - When I run `systemctl stop ubuntu-advantage.service` with sudo - When I run `systemctl disable ubuntu-advantage.service` with sudo - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` - Then stdout matches regexp: - """ - Active: inactive \(dead\) - """ - When I run `dpkg-reconfigure ubuntu-advantage-tools` with sudo - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` - Then stdout matches regexp: - """ - Active: inactive \(dead\) - """ - - # Verify manual stop & disable persists across reboot - When I reboot the machine - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` - Then stdout matches regexp: - """ - Active: inactive \(dead\) - """ - Examples: version - | release | machine_type | - | xenial | gcp.generic | - | bionic | gcp.generic | - | focal | gcp.generic | - | jammy | gcp.generic | - - @uses.config.contract_token - Scenario Outline: daemon should run when appropriate on azure generic lts - Given a `` `` machine with ubuntu-advantage-tools installed - # verify its enabled, but stops itself when not configured to poll - When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo - Then stdout contains substring: - """ - daemon starting - """ - Then stdout contains substring: - """ - Configured to not poll for pro license, shutting down - """ - Then stdout contains substring: - """ - daemon ending - """ - When I run `systemctl is-enabled ubuntu-advantage.service` with sudo - Then stdout matches regexp: - """ - enabled - """ - Then I verify that running `systemctl is-failed ubuntu-advantage.service` `with sudo` exits `1` - Then stdout matches regexp: - """ - inactive - """ - - # verify it stays on when configured to do so - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { "poll_for_pro_license": true } - """ - When I run `systemctl restart ubuntu-advantage.service` with sudo - # give it time to get past the initial request - When I wait `5` seconds - When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo - Then stdout contains substring: - """ - daemon starting - """ - Then stdout contains substring: - """ - Cancelling polling - """ - Then stdout contains substring: - """ - daemon ending - """ - When I run `systemctl is-enabled ubuntu-advantage.service` with sudo - Then stdout matches regexp: - """ - enabled - """ - Then I verify that running `systemctl is-failed ubuntu-advantage.service` `with sudo` exits `1` - Then stdout matches regexp: - """ - inactive - """ - Examples: version - | release | machine_type | - | xenial | azure.generic | - | bionic | azure.generic | - | focal | azure.generic | - | jammy | azure.generic | - - @uses.config.contract_token - Scenario Outline: daemon does not start on gcp,azure generic non lts - Given a `` `` machine with ubuntu-advantage-tools installed - When I wait `1` seconds - When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo - Then stdout contains substring: - """ - daemon starting - """ - Then stdout contains substring: - """ - Not on LTS, shutting down - """ - Then stdout contains substring: - """ - daemon ending - """ - Examples: version - | release | machine_type | - | mantic | azure.generic | - | mantic | gcp.generic | - - @uses.config.contract_token - Scenario Outline: daemon does not start when not on gcpgeneric or azuregeneric - Given a `` `` machine with ubuntu-advantage-tools installed - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` - Then stdout matches regexp: - """ - Active: inactive \(dead\) - \s*Condition: start condition failed.* - """ - When I attach `contract_token` with sudo - When I run `pro detach --assume-yes` with sudo - When I reboot the machine - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` - Then stdout matches regexp: - """ - Active: inactive \(dead\) - \s*Condition: start condition failed.* - """ - Examples: version - | release | machine_type | - | xenial | lxd-container | - | xenial | lxd-vm | - | xenial | aws.generic | - | bionic | lxd-container | - | bionic | lxd-vm | - | bionic | aws.generic | - | focal | lxd-container | - | focal | lxd-vm | - | focal | aws.generic | - | jammy | lxd-container | - | jammy | lxd-vm | - | jammy | aws.generic | - | mantic | lxd-container | - | mantic | lxd-vm | - | mantic | aws.generic | - - Scenario Outline: daemon does not start when not on gcpgeneric or azuregeneric - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - data_dir: /var/lib/ubuntu-advantage - log_level: debug - log_file: /var/log/ubuntu-advantage.log - """ - When I run `pro auto-attach` with sudo - When I run `systemctl restart ubuntu-advantage.service` with sudo - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` - Then stdout matches regexp: - """ - Active: inactive \(dead\) - \s*Condition: start condition failed.* - """ - When I reboot the machine - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` - Then stdout matches regexp: - """ - Active: inactive \(dead\) - \s*Condition: start condition failed.* - """ - Examples: version - | release | machine_type | - | xenial | aws.pro | - | bionic | aws.pro | - | focal | aws.pro | - - Scenario Outline: daemon does not start when not on gcpgeneric or azuregeneric - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - data_dir: /var/lib/ubuntu-advantage - log_level: debug - log_file: /var/log/ubuntu-advantage.log - """ - When I run `pro auto-attach` with sudo - When I run `journalctl --flush --rotate` with sudo - When I run `journalctl --vacuum-time=1s` with sudo - When I run `systemctl restart ubuntu-advantage.service` with sudo - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` - Then stdout matches regexp: - """ - Active: inactive \(dead\).* - \s*Condition: start condition failed.* - """ - When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo - Then stdout does not contain substring: - """ - daemon starting - """ - When I reboot the machine - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` - Then stdout matches regexp: - """ - Active: inactive \(dead\) - \s*Condition: start condition failed.* - """ - When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo - Then stdout does not contain substring: - """ - daemon starting - """ - Examples: version - | release | machine_type | - | xenial | azure.pro | - | xenial | gcp.pro | - | bionic | azure.pro | - | bionic | gcp.pro | - | focal | azure.pro | - | focal | gcp.pro | - - @skip_local_environment - @skip_prebuilt_environment - @uses.config.contract_token - Scenario Outline: daemon should wait for cloud-config.service to finish - Given a `` `` machine with ubuntu-advantage-tools installed adding this cloud-init user_data - """ - ubuntu_advantage: {} - """ - When I apt remove `ubuntu-advantage-tools ubuntu-pro-client` - When I run `cloud-init clean --logs` with sudo - When I reboot the machine - When I run `journalctl -b -o cat -u ubuntu-advantage.service` with sudo - Then stdout contains substring: - """ - daemon starting - """ - Then stdout contains substring: - """ - cloud-config.service is activating. waiting to check again - """ - Then stdout does not contain substring: - """ - cloud-config.service is not activating. continuing - """ - When I wait `20` seconds - When I run `journalctl -b -o cat -u ubuntu-advantage.service` with sudo - Then stdout contains substring: - """ - cloud-config.service is not activating. continuing - """ - Then stdout contains substring: - """ - checking for condition files - """ - Examples: version - | release | machine_type | - | bionic | gcp.generic | - | focal | gcp.generic | - | jammy | gcp.generic | + @uses.config.contract_token + Scenario Outline: cloud-id-shim service is not installed on anything other than xenial + Given a `` `` machine with ubuntu-advantage-tools installed + Then I verify that running `systemctl status ubuntu-advantage-cloud-id-shim.service` `with sudo` exits `4` + Then stderr matches regexp: + """ + Unit ubuntu-advantage-cloud-id-shim.service could not be found. + """ + + Examples: version + | release | machine_type | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + @uses.config.contract_token + Scenario Outline: cloud-id-shim should run in postinst and on boot + Given a `` `` machine with ubuntu-advantage-tools installed + # verify installing pro created the cloud-id file + When I run `cat /run/cloud-init/cloud-id` with sudo + Then I will see the following on stdout + """ + lxd + """ + When I run `cat /run/cloud-init/cloud-id-lxd` with sudo + Then I will see the following on stdout + """ + lxd + """ + # verify the shim service runs on boot and creates the cloud-id file + When I reboot the machine + Then I verify that running `systemctl status ubuntu-advantage-cloud-id-shim.service` `with sudo` exits `3` + Then stdout matches regexp: + """ + (code=exited, status=0/SUCCESS) + """ + When I run `cat /run/cloud-init/cloud-id` with sudo + Then I will see the following on stdout + """ + lxd + """ + When I run `cat /run/cloud-init/cloud-id-lxd` with sudo + Then I will see the following on stdout + """ + lxd + """ + + Examples: version + | release | machine_type | + | xenial | lxd-container | + + @uses.config.contract_token + Scenario Outline: daemon should run when appropriate on gcp generic lts + Given a `` `` machine with ubuntu-advantage-tools installed + # verify its enabled, but stops itself when not configured to poll + When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo + Then stdout contains substring: + """ + daemon starting + """ + Then stdout contains substring: + """ + Configured to not poll for pro license, shutting down + """ + Then stdout contains substring: + """ + daemon ending + """ + When I run `systemctl is-enabled ubuntu-advantage.service` with sudo + Then stdout matches regexp: + """ + enabled + """ + Then I verify that running `systemctl is-failed ubuntu-advantage.service` `with sudo` exits `1` + Then stdout matches regexp: + """ + inactive + """ + # verify it stays on when configured to do so + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { "poll_for_pro_license": true } + """ + # Turn on memory accounting + When I run `sed -i s/#DefaultMemoryAccounting=no/DefaultMemoryAccounting=yes/ /etc/systemd/system.conf` with sudo + When I run `systemctl daemon-reexec` with sudo + # on bionic, systemd version=237; which does not allow for log rotation + vacuum in same line e.g. + # journalctl --flush --rotate --vacuum-time=1s + When I run `journalctl --flush --rotate` with sudo + When I run `journalctl --vacuum-time=1s` with sudo + When I run `systemctl restart ubuntu-advantage.service` with sudo + # wait to get memory after it has settled/after startup checks + When I wait `5` seconds + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `0` + Then stdout matches regexp: + """ + Active: active \(running\) + """ + # TODO find out what caused memory to go up, try to lower it again + Then on `xenial`, systemd status output says memory usage is less than `17` MB + Then on `bionic`, systemd status output says memory usage is less than `15` MB + Then on `focal`, systemd status output says memory usage is less than `14` MB + Then on `jammy`, systemd status output says memory usage is less than `14` MB + Then on `noble`, systemd status output says memory usage is less than `14` MB + When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo + Then stdout contains substring: + """ + daemon starting + """ + Then stdout does not contain substring: + """ + daemon ending + """ + When I run `systemctl is-enabled ubuntu-advantage.service` with sudo + Then stdout matches regexp: + """ + enabled + """ + Then I verify that running `systemctl is-failed ubuntu-advantage.service` `with sudo` exits `1` + Then stdout matches regexp: + """ + active + """ + # verify attach stops it immediately and doesn't restart after reboot + When I attach `contract_token` with sudo + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` + Then stdout matches regexp: + """ + Active: inactive \(dead\) + """ + When I reboot the machine + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` + Then stdout matches regexp: + """ + Active: inactive \(dead\) + \s*Condition: start condition failed.* + .*ConditionPathExists=!/var/lib/ubuntu-advantage/private/machine-token.json was not met + """ + # verify detach starts it and it starts again after reboot + When I run `journalctl --flush --rotate` with sudo + When I run `journalctl --vacuum-time=1s` with sudo + When I run `pro detach --assume-yes` with sudo + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `0` + Then stdout matches regexp: + """ + Active: active \(running\) + """ + When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo + Then stdout contains substring: + """ + daemon starting + """ + Then stdout does not contain substring: + """ + daemon ending + """ + When I reboot the machine + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `0` + Then stdout matches regexp: + """ + Active: active \(running\) + """ + When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo + Then stdout contains substring: + """ + daemon starting + """ + Then stdout does not contain substring: + """ + daemon ending + """ + # Verify manual stop & disable persists across reconfigure + When I run `systemctl stop ubuntu-advantage.service` with sudo + When I run `systemctl disable ubuntu-advantage.service` with sudo + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` + Then stdout matches regexp: + """ + Active: inactive \(dead\) + """ + When I run `dpkg-reconfigure ubuntu-advantage-tools` with sudo + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` + Then stdout matches regexp: + """ + Active: inactive \(dead\) + """ + # Verify manual stop & disable persists across reboot + When I reboot the machine + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` + Then stdout matches regexp: + """ + Active: inactive \(dead\) + """ + + Examples: version + | release | machine_type | + | xenial | gcp.generic | + | bionic | gcp.generic | + | focal | gcp.generic | + | jammy | gcp.generic | + | noble | gcp.generic | + + @uses.config.contract_token + Scenario Outline: daemon should run when appropriate on azure generic lts + Given a `` `` machine with ubuntu-advantage-tools installed + # verify its enabled, but stops itself when not configured to poll + When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo + Then stdout contains substring: + """ + daemon starting + """ + Then stdout contains substring: + """ + Configured to not poll for pro license, shutting down + """ + Then stdout contains substring: + """ + daemon ending + """ + When I run `systemctl is-enabled ubuntu-advantage.service` with sudo + Then stdout matches regexp: + """ + enabled + """ + Then I verify that running `systemctl is-failed ubuntu-advantage.service` `with sudo` exits `1` + Then stdout matches regexp: + """ + inactive + """ + # verify it stays on when configured to do so + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { "poll_for_pro_license": true } + """ + When I run `systemctl restart ubuntu-advantage.service` with sudo + # give it time to get past the initial request + When I wait `5` seconds + When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo + Then stdout contains substring: + """ + daemon starting + """ + Then stdout contains substring: + """ + Cancelling polling + """ + Then stdout contains substring: + """ + daemon ending + """ + When I run `systemctl is-enabled ubuntu-advantage.service` with sudo + Then stdout matches regexp: + """ + enabled + """ + Then I verify that running `systemctl is-failed ubuntu-advantage.service` `with sudo` exits `1` + Then stdout matches regexp: + """ + inactive + """ + + Examples: version + | release | machine_type | + | xenial | azure.generic | + | bionic | azure.generic | + | focal | azure.generic | + | jammy | azure.generic | + | noble | azure.generic | + + @uses.config.contract_token + Scenario Outline: daemon does not start on gcp,azure generic non lts + Given a `` `` machine with ubuntu-advantage-tools installed + When I wait `1` seconds + When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo + Then stdout contains substring: + """ + daemon starting + """ + Then stdout contains substring: + """ + Not on LTS, shutting down + """ + Then stdout contains substring: + """ + daemon ending + """ + + Examples: version + | release | machine_type | + | mantic | azure.generic | + | mantic | gcp.generic | + + @uses.config.contract_token + Scenario Outline: daemon does not start when not on gcpgeneric or azuregeneric + Given a `` `` machine with ubuntu-advantage-tools installed + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` + Then stdout matches regexp: + """ + Active: inactive \(dead\) + \s*Condition: start condition (failed|unmet).* + """ + When I attach `contract_token` with sudo + When I run `pro detach --assume-yes` with sudo + When I reboot the machine + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` + Then stdout matches regexp: + """ + Active: inactive \(dead\) + \s*Condition: start condition (failed|unmet).* + """ + + Examples: version + | release | machine_type | + | xenial | lxd-container | + | xenial | lxd-vm | + | xenial | aws.generic | + | bionic | lxd-container | + | bionic | lxd-vm | + | bionic | aws.generic | + | focal | lxd-container | + | focal | lxd-vm | + | focal | aws.generic | + | jammy | lxd-container | + | jammy | lxd-vm | + | jammy | aws.generic | + | mantic | lxd-container | + | mantic | lxd-vm | + | mantic | aws.generic | + | noble | lxd-container | + | noble | lxd-vm | + | noble | aws.generic | + + Scenario Outline: daemon does not start when not on gcpgeneric or azuregeneric + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + data_dir: /var/lib/ubuntu-advantage + log_level: debug + log_file: /var/log/ubuntu-advantage.log + """ + When I run `pro auto-attach` with sudo + When I run `systemctl restart ubuntu-advantage.service` with sudo + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` + Then stdout matches regexp: + """ + Active: inactive \(dead\) + \s*Condition: start condition failed.* + """ + When I reboot the machine + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` + Then stdout matches regexp: + """ + Active: inactive \(dead\) + \s*Condition: start condition failed.* + """ + + Examples: version + | release | machine_type | + | xenial | aws.pro | + | bionic | aws.pro | + | focal | aws.pro | + + Scenario Outline: daemon does not start when not on gcpgeneric or azuregeneric + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + data_dir: /var/lib/ubuntu-advantage + log_level: debug + log_file: /var/log/ubuntu-advantage.log + """ + When I run `pro auto-attach` with sudo + When I run `journalctl --flush --rotate` with sudo + When I run `journalctl --vacuum-time=1s` with sudo + When I run `systemctl restart ubuntu-advantage.service` with sudo + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` + Then stdout matches regexp: + """ + Active: inactive \(dead\).* + \s*Condition: start condition failed.* + .*ConditionPathExists=!/var/lib/ubuntu-advantage/private/machine-token.json was not met + """ + When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo + Then stdout does not contain substring: + """ + daemon starting + """ + When I reboot the machine + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` + Then stdout matches regexp: + """ + Active: inactive \(dead\) + \s*Condition: start condition failed.* + .*ConditionPathExists=!/var/lib/ubuntu-advantage/private/machine-token.json was not met + """ + When I run `journalctl -o cat -u ubuntu-advantage.service` with sudo + Then stdout does not contain substring: + """ + daemon starting + """ + + Examples: version + | release | machine_type | + | xenial | azure.pro | + | xenial | gcp.pro | + | bionic | azure.pro | + | bionic | gcp.pro | + | focal | azure.pro | + | focal | gcp.pro | + + @skip_local_environment @skip_prebuilt_environment @uses.config.contract_token + Scenario Outline: daemon should wait for cloud-config.service to finish + Given a `` `` machine with ubuntu-advantage-tools installed adding this cloud-init user_data + """ + ubuntu_advantage: {} + """ + When I apt remove `ubuntu-advantage-tools ubuntu-pro-client` + When I run `cloud-init clean --logs` with sudo + When I reboot the machine + When I run `journalctl -b -o cat -u ubuntu-advantage.service` with sudo + Then stdout contains substring: + """ + daemon starting + """ + Then stdout contains substring: + """ + cloud-config.service is activating. waiting to check again + """ + Then stdout does not contain substring: + """ + cloud-config.service is not activating. continuing + """ + When I wait `20` seconds + When I run `journalctl -b -o cat -u ubuntu-advantage.service` with sudo + Then stdout contains substring: + """ + cloud-config.service is not activating. continuing + """ + Then stdout contains substring: + """ + checking for condition files + """ + + Examples: version + | release | machine_type | + | bionic | gcp.generic | + | focal | gcp.generic | + | jammy | gcp.generic | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/detached_auto_attach.feature ubuntu-advantage-tools-32~16.04/features/detached_auto_attach.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/detached_auto_attach.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/detached_auto_attach.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,32 +1,35 @@ @uses.config.contract_token Feature: Attached cloud does not detach when auto-attaching after manually attaching - Scenario Outline: No detaching on manually attached machine on all clouds - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `pro refresh` with sudo - Then I will see the following on stdout: - """ - Successfully processed your pro configuration. - Successfully refreshed your subscription. - Successfully updated Ubuntu Pro related APT and MOTD messages. - """ - When I verify that running `pro auto-attach` `with sudo` exits `2` - Then stderr matches regexp: - """ - This machine is already attached to '.+' - To use a different subscription first run: sudo pro detach. - """ - And I verify that `esm-infra` is enabled + Scenario Outline: No detaching on manually attached machine on all clouds + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `pro refresh` with sudo + Then I will see the following on stdout: + """ + Successfully processed your pro configuration. + Successfully refreshed your subscription. + Successfully updated Ubuntu Pro related APT and MOTD messages. + """ + When I verify that running `pro auto-attach` `with sudo` exits `2` + Then stderr matches regexp: + """ + This machine is already attached to '.+' + To use a different subscription first run: sudo pro detach. + """ + And I verify that `esm-infra` is enabled - Examples: ubuntu release - | release | machine_type | - | xenial | aws.generic | - | xenial | azure.generic | - | xenial | gcp.generic | - | bionic | aws.generic | - | bionic | azure.generic | - | bionic | gcp.generic | - | focal | aws.generic | - | focal | azure.generic | - | focal | gcp.generic | + Examples: ubuntu release + | release | machine_type | + | xenial | aws.generic | + | xenial | azure.generic | + | xenial | gcp.generic | + | bionic | aws.generic | + | bionic | azure.generic | + | bionic | gcp.generic | + | focal | aws.generic | + | focal | azure.generic | + | focal | gcp.generic | + | noble | aws.generic | + | noble | azure.generic | + | noble | gcp.generic | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/docker.feature ubuntu-advantage-tools-32~16.04/features/docker.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/docker.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/docker.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,168 +1,165 @@ Feature: Build docker images with pro services - @slow - @uses.config.contract_token - Scenario Outline: Build docker images with pro services - Given a `` `` machine with ubuntu-advantage-tools installed - When I have the `` debs under test in `/home/ubuntu` - When I apt install `docker.io docker-buildx jq` - When I create the file `/home/ubuntu/Dockerfile` with the following: - """ - FROM ubuntu: - - COPY ./ubuntu-advantage-tools.deb /ua.deb - COPY ./ubuntu-pro-client.deb /pro.deb - - RUN --mount=type=secret,id=ua-attach-config \ - apt-get update \ - && apt-get install --no-install-recommends -y ubuntu-advantage-tools ca-certificates \ - - && ((apt install /ua.deb /pro.deb -y || true)) \ - - && apt-get install -f \ - - && pro attach --attach-config /run/secrets/ua-attach-config \ - - # Normally an apt upgrade is recommended, but we dont do that here - # in order to measure the image size bloat from just the enablement - # process - # && apt-get upgrade -y \ - - && apt-get install -y \ - - # If you need ca-certificates, remove it from this line - && apt-get purge --auto-remove -y ubuntu-advantage-tools ca-certificates \ - - && rm -rf /var/lib/apt/lists/* - """ - When I create the file `/home/ubuntu/ua-attach-config.yaml` with the following: - """ - token: - enable_services: - """ - When I replace `` in `/home/ubuntu/ua-attach-config.yaml` with token `contract_token` - - # Build succeeds - When I run shell command `DOCKER_BUILDKIT=1 docker build . --secret id=ua-attach-config,src=ua-attach-config.yaml -t ua-test` with sudo - - # Bloat is minimal (new size == original size + deb size + test package size) - Then docker image `ua-test` is not significantly larger than `ubuntu:` with `` installed - - # No secrets or artifacts leftover - Then `90ubuntu-advantage` is not present in any docker image layer - Then `machine-token.json` is not present in any docker image layer - Then `ubuntu-advantage.log` is not present in any docker image layer - Then `uaclient.conf` is not present in any docker image layer - - # Service successfully enabled (Correct version of package installed) - When I run `docker run ua-test dpkg-query --showformat='${Version}' --show ` with sudo - Then stdout matches regexp: - """ - - """ - - # Invalid attach config file causes build to fail - When I create the file `/home/ubuntu/ua-attach-config.yaml` with the following: - """ - token: - enable_services: { fips: true } - """ - When I replace `` in `/home/ubuntu/ua-attach-config.yaml` with token `contract_token` - Then I verify that running `DOCKER_BUILDKIT=1 docker build . --no-cache --secret id=ua-attach-config,src=ua-attach-config.yaml -t ua-test` `with sudo` exits `1` - - Examples: ubuntu release - | release | machine_type | container_release |enable_services | test_package_name | test_package_version | - | mantic | lxd-vm | xenial | [ esm-infra ] | curl | esm | - | mantic | lxd-vm | bionic | [ fips ] | openssl | fips | - | mantic | lxd-vm | focal | [ esm-apps ] | hello | esm | - - Scenario Outline: Build pro docker images auto-attached instances - settings_overrides method - Given a `` `` machine with ubuntu-advantage-tools installed - When I have the `` debs under test in `/home/ubuntu` - When I run `apt-get update` with sudo - When I apt install `docker.io docker-buildx` - When I create the file `/home/ubuntu/Dockerfile` with the following: - """ - FROM ubuntu: - ARG PRO_CLOUD_OVERRIDE= - - COPY ./ubuntu-advantage-tools.deb /ua.deb - COPY ./ubuntu-pro-client.deb /pro.deb - - RUN --mount=type=secret,id=ua-attach-config \ - apt-get update \ - && apt-get install --no-install-recommends -y ubuntu-advantage-tools ca-certificates \ - - && ((apt install /ua.deb /pro.deb -y || true)) \ - - && apt-get install -f \ - - && echo "settings_overrides: { cloud_type: $PRO_CLOUD_OVERRIDE }" >> /etc/ubuntu-advantage/uaclient.conf \ - && pro api u.pro.attach.auto.full_auto_attach.v1 --data '{"enable": }' \ - - && apt-get install -y \ - - # If you need ca-certificates, remove it from this line - && apt-get purge --auto-remove -y ubuntu-advantage-tools ca-certificates \ - - && rm -rf /var/lib/apt/lists/* - """ - # Build succeeds - When I run shell command `DOCKER_BUILDKIT=1 docker build . -t test --build-arg PRO_CLOUD_OVERRIDE= ` with sudo - - # Service successfully enabled (Correct version of package installed) - When I run `docker run test dpkg-query --showformat='${Version}' --show ` with sudo - Then stdout matches regexp: - """ - - """ - Examples: ubuntu release - | release | machine_type | cloud_override | container_release | enable_services | test_package_name | test_package_version | extra_build_args | - | jammy | aws.pro | aws | xenial | [ "esm-infra" ] | curl | esm | --network=host | - | jammy | azure.pro | azure | bionic | [ "fips" ] | openssl | fips | | - | jammy | gcp.pro | gce | focal | [ "esm-apps" ] | hello | esm | | - - Scenario Outline: Build pro docker images auto-attached instances - API arg method - Given a `` `` machine with ubuntu-advantage-tools installed - When I have the `` debs under test in `/home/ubuntu` - When I run `apt-get update` with sudo - When I apt install `docker.io docker-buildx` - When I create the file `/home/ubuntu/Dockerfile` with the following: - """ - FROM ubuntu: - ARG PRO_CLOUD_OVERRIDE= - - COPY ./ubuntu-advantage-tools.deb /ua.deb - COPY ./ubuntu-pro-client.deb /pro.deb - - RUN --mount=type=secret,id=ua-attach-config \ - apt-get update \ - && apt-get install --no-install-recommends -y ubuntu-advantage-tools ca-certificates \ - - && ((apt install /ua.deb /pro.deb -y || true)) \ - - && apt-get install -f \ - - && pro --debug api u.pro.attach.auto.full_auto_attach.v1 --data "{\"cloud_override\": \"$PRO_CLOUD_OVERRIDE\", \"enable\": }" \ - - && apt-get install -y \ - - # If you need ca-certificates, remove it from this line - && apt-get purge --auto-remove -y ubuntu-advantage-tools ca-certificates \ - - && rm -rf /var/lib/apt/lists/* - """ - # Build succeeds - When I run shell command `DOCKER_BUILDKIT=1 docker build . -t test --build-arg PRO_CLOUD_OVERRIDE= ` with sudo - - # Service successfully enabled (Correct version of package installed) - When I run `docker run test dpkg-query --showformat='${Version}' --show ` with sudo - Then stdout matches regexp: - """ - - """ - Examples: ubuntu release - | release | machine_type | cloud_override | container_release | enable_services | test_package_name | test_package_version | extra_build_args | - | jammy | aws.pro | aws | xenial | [ \"esm-infra\" ] | curl | esm | --network=host | - | jammy | azure.pro | azure | bionic | [ \"fips\" ] | openssl | fips | | - | jammy | gcp.pro | gce | focal | [ \"esm-apps\" ] | hello | esm | | + @slow @uses.config.contract_token + Scenario Outline: Build docker images with pro services + Given a `` `` machine with ubuntu-advantage-tools installed + When I have the `` debs under test in `/home/ubuntu` + When I apt install `docker.io docker-buildx jq` + When I create the file `/home/ubuntu/Dockerfile` with the following: + """ + FROM ubuntu: + + COPY ./ubuntu-advantage-tools.deb /ua.deb + COPY ./ubuntu-pro-client.deb /pro.deb + + RUN --mount=type=secret,id=ua-attach-config \ + apt-get update \ + && apt-get install --no-install-recommends -y ubuntu-advantage-tools ca-certificates \ + + && ((apt install /ua.deb /pro.deb -y || true)) \ + + && apt-get install -f \ + + && pro attach --attach-config /run/secrets/ua-attach-config \ + + # Normally an apt upgrade is recommended, but we dont do that here + # in order to measure the image size bloat from just the enablement + # process + # && apt-get upgrade -y \ + + && apt-get install -y \ + + # If you need ca-certificates, remove it from this line + && apt-get purge --auto-remove -y ubuntu-advantage-tools ubuntu-pro-client ca-certificates \ + + && rm -rf /var/lib/apt/lists/* + """ + When I create the file `/home/ubuntu/ua-attach-config.yaml` with the following: + """ + token: + enable_services: + """ + When I replace `` in `/home/ubuntu/ua-attach-config.yaml` with token `contract_token` + # Build succeeds + When I run shell command `DOCKER_BUILDKIT=1 docker build . --secret id=ua-attach-config,src=ua-attach-config.yaml -t ua-test` with sudo + # Bloat is minimal (new size == original size + deb size + test package size) + Then docker image `ua-test` is not significantly larger than `ubuntu:` with `` installed + # No secrets or artifacts leftover + Then `90ubuntu-advantage` is not present in any docker image layer + Then `machine-token.json` is not present in any docker image layer + Then `ubuntu-advantage.log` is not present in any docker image layer + Then `uaclient.conf` is not present in any docker image layer + # Service successfully enabled (Correct version of package installed) + When I run `docker run ua-test dpkg-query --showformat='${Version}' --show ` with sudo + Then stdout matches regexp: + """ + + """ + # Invalid attach config file causes build to fail + When I create the file `/home/ubuntu/ua-attach-config.yaml` with the following: + """ + token: + enable_services: { fips: true } + """ + When I replace `` in `/home/ubuntu/ua-attach-config.yaml` with token `contract_token` + Then I verify that running `DOCKER_BUILDKIT=1 docker build . --no-cache --secret id=ua-attach-config,src=ua-attach-config.yaml -t ua-test` `with sudo` exits `1` + + Examples: ubuntu release + | release | machine_type | container_release | enable_services | test_package_name | test_package_version | + | mantic | lxd-vm | xenial | [ esm-infra ] | curl | esm | + | mantic | lxd-vm | bionic | [ fips ] | openssl | fips | + | mantic | lxd-vm | focal | [ esm-apps ] | hello | esm | + | noble | lxd-vm | xenial | [ esm-infra ] | curl | esm | + | noble | lxd-vm | bionic | [ fips ] | openssl | fips | + | noble | lxd-vm | focal | [ esm-apps ] | hello | esm | + + Scenario Outline: Build pro docker images auto-attached instances - settings_overrides method + Given a `` `` machine with ubuntu-advantage-tools installed + When I have the `` debs under test in `/home/ubuntu` + When I run `apt-get update` with sudo + When I apt install `docker.io docker-buildx` + When I create the file `/home/ubuntu/Dockerfile` with the following: + """ + FROM ubuntu: + ARG PRO_CLOUD_OVERRIDE= + + COPY ./ubuntu-advantage-tools.deb /ua.deb + COPY ./ubuntu-pro-client.deb /pro.deb + + RUN --mount=type=secret,id=ua-attach-config \ + apt-get update \ + && apt-get install --no-install-recommends -y ubuntu-advantage-tools ca-certificates \ + + && ((apt install /ua.deb /pro.deb -y || true)) \ + + && apt-get install -f \ + + && echo "settings_overrides: { cloud_type: $PRO_CLOUD_OVERRIDE }" >> /etc/ubuntu-advantage/uaclient.conf \ + && pro api u.pro.attach.auto.full_auto_attach.v1 --data '{"enable": }' \ + + && apt-get install -y \ + + # If you need ca-certificates, remove it from this line + && apt-get purge --auto-remove -y ubuntu-advantage-tools ubuntu-pro-client ca-certificates \ + + && rm -rf /var/lib/apt/lists/* + """ + # Build succeeds + When I run shell command `DOCKER_BUILDKIT=1 docker build . -t test --build-arg PRO_CLOUD_OVERRIDE= ` with sudo + # Service successfully enabled (Correct version of package installed) + When I run `docker run test dpkg-query --showformat='${Version}' --show ` with sudo + Then stdout matches regexp: + """ + + """ + + Examples: ubuntu release + | release | machine_type | cloud_override | container_release | enable_services | test_package_name | test_package_version | extra_build_args | + | jammy | aws.pro | aws | xenial | [ "esm-infra" ] | curl | esm | --network=host | + | jammy | azure.pro | azure | bionic | [ "fips" ] | openssl | fips | | + | jammy | gcp.pro | gce | focal | [ "esm-apps" ] | hello | esm | | + + Scenario Outline: Build pro docker images auto-attached instances - API arg method + Given a `` `` machine with ubuntu-advantage-tools installed + When I have the `` debs under test in `/home/ubuntu` + When I run `apt-get update` with sudo + When I apt install `docker.io docker-buildx` + When I create the file `/home/ubuntu/Dockerfile` with the following: + """ + FROM ubuntu: + ARG PRO_CLOUD_OVERRIDE= + + COPY ./ubuntu-advantage-tools.deb /ua.deb + COPY ./ubuntu-pro-client.deb /pro.deb + + RUN --mount=type=secret,id=ua-attach-config \ + apt-get update \ + && apt-get install --no-install-recommends -y ubuntu-advantage-tools ca-certificates \ + + && ((apt install /ua.deb /pro.deb -y || true)) \ + + && apt-get install -f \ + + && pro --debug api u.pro.attach.auto.full_auto_attach.v1 --data "{\"cloud_override\": \"$PRO_CLOUD_OVERRIDE\", \"enable\": }" \ + + && apt-get install -y \ + + # If you need ca-certificates, remove it from this line + && apt-get purge --auto-remove -y ubuntu-advantage-tools ubuntu-pro-client ca-certificates \ + + && rm -rf /var/lib/apt/lists/* + """ + # Build succeeds + When I run shell command `DOCKER_BUILDKIT=1 docker build . -t test --build-arg PRO_CLOUD_OVERRIDE= ` with sudo + # Service successfully enabled (Correct version of package installed) + When I run `docker run test dpkg-query --showformat='${Version}' --show ` with sudo + Then stdout matches regexp: + """ + + """ + + Examples: ubuntu release + | release | machine_type | cloud_override | container_release | enable_services | test_package_name | test_package_version | extra_build_args | + | jammy | aws.pro | aws | xenial | [ \\"esm-infra\\" ] | curl | esm | --network=host | + | jammy | azure.pro | azure | bionic | [ \\"fips\\" ] | openssl | fips | | + | jammy | gcp.pro | gce | focal | [ \\"esm-apps\\" ] | hello | esm | | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/enable_fips_cloud.feature ubuntu-advantage-tools-32~16.04/features/enable_fips_cloud.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/enable_fips_cloud.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/enable_fips_cloud.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,277 +1,295 @@ @uses.config.contract_token Feature: FIPS enablement in cloud based machines - Scenario Outline: Attached enable of FIPS services in an ubuntu gcp vm - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that running `pro enable --assume-yes` `with sudo` exits `1` - And stdout matches regexp: - """ - Ubuntu does not provide a GCP optimized FIPS kernel - """ - - Examples: fips - | release | machine_type | release_title | fips_service | - | xenial | gcp.generic | Xenial | fips | - | xenial | gcp.generic | Xenial | fips-updates | - - Scenario Outline: FIPS unholds packages - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I apt install `openssh-client openssh-server strongswan` - And I run `apt-mark hold openssh-client openssh-server strongswan` with sudo - And I run `pro enable fips --assume-yes` with sudo - Then I verify that `openssh-server` is installed from apt source `` - And I verify that `openssh-client` is installed from apt source `` - And I verify that `strongswan` is installed from apt source `` - And I verify that `openssh-server-hmac` is installed from apt source `` - And I verify that `openssh-client-hmac` is installed from apt source `` - And I verify that `strongswan-hmac` is installed from apt source `` - When I run `pro disable fips --assume-yes` with sudo - And I run `apt-mark unhold openssh-client openssh-server strongswan` with sudo - Then I will see the following on stdout: - """ - openssh-client was already not hold. - openssh-server was already not hold. - strongswan was already not hold. - """ - When I reboot the machine - Then I verify that `openssh-server` installed version matches regexp `fips` - And I verify that `openssh-client` installed version matches regexp `fips` - And I verify that `strongswan` installed version matches regexp `fips` - And I verify that `openssh-server-hmac` installed version matches regexp `fips` - And I verify that `openssh-client-hmac` installed version matches regexp `fips` - And I verify that `strongswan-hmac` installed version matches regexp `fips` - - Examples: ubuntu release - | release | machine_type | fips-apt-source | - | xenial | aws.generic | https://esm.ubuntu.com/fips/ubuntu xenial/main | - | xenial | azure.generic | https://esm.ubuntu.com/fips/ubuntu xenial/main | - - - Scenario Outline: FIPS unholds packages - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I apt install `openssh-client openssh-server strongswan` - And I run `apt-mark hold openssh-client openssh-server strongswan` with sudo - And I run `pro enable fips --assume-yes` with sudo - Then I verify that `openssh-server` is installed from apt source `` - And I verify that `openssh-client` is installed from apt source `` - And I verify that `strongswan` is installed from apt source `` - And I verify that `openssh-server-hmac` is installed from apt source `` - And I verify that `openssh-client-hmac` is installed from apt source `` - And I verify that `strongswan-hmac` is installed from apt source `` - When I run `pro disable fips --assume-yes` with sudo - And I run `apt-mark unhold openssh-client openssh-server strongswan` with sudo - Then I will see the following on stdout: - """ - openssh-client was already not hold. - openssh-server was already not hold. - strongswan was already not hold. - """ - When I reboot the machine - Then I verify that `openssh-server` installed version matches regexp `fips` - And I verify that `openssh-client` installed version matches regexp `fips` - And I verify that `strongswan` installed version matches regexp `fips` - And I verify that `openssh-server-hmac` installed version matches regexp `fips` - And I verify that `openssh-client-hmac` installed version matches regexp `fips` - And I verify that `strongswan-hmac` installed version matches regexp `fips` - - Examples: ubuntu release - | release | machine_type | fips-apt-source | - | bionic | aws.generic | https://esm.ubuntu.com/fips/ubuntu bionic/main | - | bionic | azure.generic | https://esm.ubuntu.com/fips/ubuntu bionic/main | - | bionic | gcp.generic | https://esm.ubuntu.com/fips/ubuntu bionic/main | - - Scenario Outline: FIPS unholds packages - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I apt install `openssh-client openssh-server strongswan` - And I run `apt-mark hold openssh-client openssh-server strongswan` with sudo - And I run `pro enable fips --assume-yes` with sudo - Then I verify that `openssh-server` is installed from apt source `` - And I verify that `openssh-client` is installed from apt source `` - And I verify that `strongswan` is installed from apt source `` - And I verify that `strongswan-hmac` is installed from apt source `` - When I run `pro disable fips --assume-yes` with sudo - And I run `apt-mark unhold openssh-client openssh-server strongswan` with sudo - Then I will see the following on stdout: - """ - openssh-client was already not hold. - openssh-server was already not hold. - strongswan was already not hold. - """ - When I reboot the machine - Then I verify that `openssh-server` installed version matches regexp `fips` - And I verify that `openssh-client` installed version matches regexp `fips` - And I verify that `strongswan` installed version matches regexp `fips` - And I verify that `strongswan-hmac` installed version matches regexp `fips` - - Examples: ubuntu release - | release | machine_type | fips-apt-source | - | focal | aws.generic | https://esm.ubuntu.com/fips/ubuntu focal/main | - | focal | azure.generic | https://esm.ubuntu.com/fips/ubuntu focal/main | - | focal | gcp.generic | https://esm.ubuntu.com/fips/ubuntu focal/main | - - @slow - Scenario Outline: Enable FIPS in a cloud VM - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `pro enable --assume-yes` with sudo - Then stdout contains substring: - """ - Updating package lists - Installing packages - Updating standard Ubuntu package lists - enabled - A reboot is required to complete install - """ - And I verify that `` is enabled - And I ensure apt update runs without errors - And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1` - When I run `apt-cache policy ` as non-root - Then stdout does not match regexp: - """ - .*Installed: \(none\) - """ - When I reboot the machine - And I run `uname -r` as non-root - Then stdout matches regexp: - """ - - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ - And I verify that `` is installed from apt source `` - When I run `pro disable --assume-yes` with sudo - Then stdout matches regexp: - """ - Updating package lists - """ - When I run `apt-cache policy ` as non-root - Then stdout matches regexp: - """ - .*Installed: \(none\) - """ - When I reboot the machine - Then I verify that `` is disabled - - Examples: ubuntu release - | release | machine_type | fips-name | fips-service | fips-package | fips-kernel | fips-apt-source | - | xenial | azure.generic | FIPS | fips | ubuntu-fips | fips | https://esm.ubuntu.com/fips/ubuntu xenial/main | - | xenial | azure.generic | FIPS Updates | fips-updates | ubuntu-fips | fips | https://esm.ubuntu.com/fips-updates/ubuntu xenial-updates/main | - | xenial | aws.generic | FIPS | fips | ubuntu-fips | fips | https://esm.ubuntu.com/fips/ubuntu xenial/main | - | bionic | azure.generic | FIPS | fips | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | - | bionic | azure.generic | FIPS Updates | fips-updates | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | - | bionic | aws.generic | FIPS | fips | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | - | bionic | aws.generic | FIPS Updates | fips-updates | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | - | bionic | gcp.generic | FIPS | fips | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | - | bionic | gcp.generic | FIPS Updates | fips-updates | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | - | focal | azure.generic | FIPS | fips | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | - | focal | azure.generic | FIPS Updates | fips-updates | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | - | focal | aws.generic | FIPS | fips | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | - | focal | aws.generic | FIPS Updates | fips-updates | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | - | focal | gcp.generic | FIPS | fips | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | - | focal | gcp.generic | FIPS Updates | fips-updates | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | - | jammy | azure.generic | FIPS Preview | fips-preview | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips-preview/ubuntu jammy/main | - | jammy | azure.generic | FIPS Updates | fips-updates | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips-updates/ubuntu jammy-updates/main | - | jammy | aws.generic | FIPS Preview | fips-preview | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips-preview/ubuntu jammy/main | - | jammy | aws.generic | FIPS Updates | fips-updates | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips-updates/ubuntu jammy-updates/main | - | jammy | gcp.generic | FIPS Preview | fips-preview | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips-preview/ubuntu jammy/main | - | jammy | gcp.generic | FIPS Updates | fips-updates | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips-updates/ubuntu jammy-updates/main | - - @slow - Scenario Outline: Attached enable of FIPS in an ubuntu image with cloud-init disabled - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `touch /etc/cloud/cloud-init.disabled` with sudo - And I reboot the machine - And I verify that running `cloud-id` `with sudo` exits `1` - Then stderr matches regexp: - """ - File not found '/run/cloud-init/instance-data.json'. Provide a path to instance data json file using --instance-data - """ - When I attach `contract_token` with sudo - And I run `pro enable fips --assume-yes` with sudo - Then stdout contains substring: - """ - Could not determine cloud, defaulting to generic FIPS package. - Updating FIPS package lists - Installing FIPS packages - Updating standard Ubuntu package lists - FIPS enabled - A reboot is required to complete install. - """ - When I run `apt-cache policy ubuntu-fips` as non-root - Then stdout does not match regexp: - """ - .*Installed: \(none\) - """ - When I reboot the machine - And I run `uname -r` as non-root - Then stdout does not match regexp: - """ - aws-fips - """ - And stdout matches regexp: - """ - fips - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | aws.generic | - - @slow - Scenario Outline: Attached enable of FIPS in an ubuntu image with cloud-init disabled - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `touch /etc/cloud/cloud-init.disabled` with sudo - And I reboot the machine - And I verify that running `cloud-id` `with sudo` exits `2` - Then I will see the following on stdout: - """ - disabled - """ - When I attach `contract_token` with sudo - And I run `pro enable fips --assume-yes` with sudo - Then stdout matches regexp: - """ - Could not determine cloud, defaulting to generic FIPS package. - Updating FIPS package lists - Installing FIPS packages - Updating standard Ubuntu package lists - FIPS enabled - A reboot is required to complete install. - """ - When I run `apt-cache policy ubuntu-fips` as non-root - Then stdout does not match regexp: - """ - .*Installed: \(none\) - """ - When I reboot the machine - And I run `uname -r` as non-root - Then stdout does not match regexp: - """ - aws-fips - """ - And stdout matches regexp: - """ - fips - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ - - Examples: ubuntu release - | release | machine_type | - | bionic | aws.generic | - | focal | aws.generic | + Scenario Outline: Attached enable of FIPS services in an ubuntu gcp vm + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that running `pro enable --assume-yes` `with sudo` exits `1` + And stdout matches regexp: + """ + Ubuntu does not provide a GCP optimized FIPS kernel + """ + + Examples: fips + | release | machine_type | release_title | fips_service | + | xenial | gcp.generic | Xenial | fips | + | xenial | gcp.generic | Xenial | fips-updates | + + Scenario Outline: FIPS unholds packages + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I apt install `openssh-client openssh-server strongswan` + And I run `apt-mark hold openssh-client openssh-server strongswan` with sudo + And I run `pro enable fips --assume-yes` with sudo + Then I verify that `openssh-server` is installed from apt source `` + And I verify that `openssh-client` is installed from apt source `` + And I verify that `strongswan` is installed from apt source `` + And I verify that `openssh-server-hmac` is installed from apt source `` + And I verify that `openssh-client-hmac` is installed from apt source `` + And I verify that `strongswan-hmac` is installed from apt source `` + When I run `pro disable fips --assume-yes` with sudo + And I run `apt-mark unhold openssh-client openssh-server strongswan` with sudo + Then I will see the following on stdout: + """ + openssh-client was already not hold. + openssh-server was already not hold. + strongswan was already not hold. + """ + When I reboot the machine + Then I verify that `openssh-server` installed version matches regexp `fips` + And I verify that `openssh-client` installed version matches regexp `fips` + And I verify that `strongswan` installed version matches regexp `fips` + And I verify that `openssh-server-hmac` installed version matches regexp `fips` + And I verify that `openssh-client-hmac` installed version matches regexp `fips` + And I verify that `strongswan-hmac` installed version matches regexp `fips` + + Examples: ubuntu release + | release | machine_type | fips-apt-source | + | xenial | aws.generic | https://esm.ubuntu.com/fips/ubuntu xenial/main | + | xenial | azure.generic | https://esm.ubuntu.com/fips/ubuntu xenial/main | + + Scenario Outline: FIPS unholds packages + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I apt install `openssh-client openssh-server strongswan` + And I run `apt-mark hold openssh-client openssh-server strongswan` with sudo + And I run `pro enable fips --assume-yes` with sudo + Then I verify that `openssh-server` is installed from apt source `` + And I verify that `openssh-client` is installed from apt source `` + And I verify that `strongswan` is installed from apt source `` + And I verify that `openssh-server-hmac` is installed from apt source `` + And I verify that `openssh-client-hmac` is installed from apt source `` + And I verify that `strongswan-hmac` is installed from apt source `` + When I run `pro disable fips --assume-yes` with sudo + And I run `apt-mark unhold openssh-client openssh-server strongswan` with sudo + Then I will see the following on stdout: + """ + openssh-client was already not hold. + openssh-server was already not hold. + strongswan was already not hold. + """ + When I reboot the machine + Then I verify that `openssh-server` installed version matches regexp `fips` + And I verify that `openssh-client` installed version matches regexp `fips` + And I verify that `strongswan` installed version matches regexp `fips` + And I verify that `openssh-server-hmac` installed version matches regexp `fips` + And I verify that `openssh-client-hmac` installed version matches regexp `fips` + And I verify that `strongswan-hmac` installed version matches regexp `fips` + + Examples: ubuntu release + | release | machine_type | fips-apt-source | + | bionic | aws.generic | https://esm.ubuntu.com/fips/ubuntu bionic/main | + | bionic | azure.generic | https://esm.ubuntu.com/fips/ubuntu bionic/main | + | bionic | gcp.generic | https://esm.ubuntu.com/fips/ubuntu bionic/main | + + Scenario Outline: FIPS unholds packages + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I apt install `openssh-client openssh-server strongswan` + And I run `apt-mark hold openssh-client openssh-server strongswan` with sudo + And I run `pro enable fips --assume-yes` with sudo + Then I verify that `openssh-server` is installed from apt source `` + And I verify that `openssh-client` is installed from apt source `` + And I verify that `strongswan` is installed from apt source `` + And I verify that `strongswan-hmac` is installed from apt source `` + When I run `pro disable fips --assume-yes` with sudo + And I run `apt-mark unhold openssh-client openssh-server strongswan` with sudo + Then I will see the following on stdout: + """ + openssh-client was already not hold. + openssh-server was already not hold. + strongswan was already not hold. + """ + When I reboot the machine + Then I verify that `openssh-server` installed version matches regexp `fips` + And I verify that `openssh-client` installed version matches regexp `fips` + And I verify that `strongswan` installed version matches regexp `fips` + And I verify that `strongswan-hmac` installed version matches regexp `fips` + + Examples: ubuntu release + | release | machine_type | fips-apt-source | + | focal | aws.generic | https://esm.ubuntu.com/fips/ubuntu focal/main | + | focal | azure.generic | https://esm.ubuntu.com/fips/ubuntu focal/main | + | focal | gcp.generic | https://esm.ubuntu.com/fips/ubuntu focal/main | + + @slow + Scenario Outline: Enable FIPS in a cloud VM + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `pro enable --assume-yes` with sudo + Then stdout contains substring: + """ + Updating package lists + Installing packages + Updating standard Ubuntu package lists + enabled + A reboot is required to complete install + """ + And I verify that `` is enabled + And I ensure apt update runs without errors + And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1` + When I run `apt-cache policy ` as non-root + Then stdout does not match regexp: + """ + .*Installed: \(none\) + """ + When I reboot the machine + And I run `uname -r` as non-root + Then stdout matches regexp: + """ + + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ + And I verify that `` is installed from apt source `` + When I run `pro disable --assume-yes` with sudo + Then stdout matches regexp: + """ + Updating package lists + """ + When I run `apt-cache policy ` as non-root + Then stdout matches regexp: + """ + .*Installed: \(none\) + """ + When I reboot the machine + Then I verify that `` is disabled + + Examples: ubuntu release + | release | machine_type | fips-name | fips-service | fips-package | fips-kernel | fips-apt-source | + | xenial | azure.generic | FIPS | fips | ubuntu-fips | fips | https://esm.ubuntu.com/fips/ubuntu xenial/main | + | xenial | azure.generic | FIPS Updates | fips-updates | ubuntu-fips | fips | https://esm.ubuntu.com/fips-updates/ubuntu xenial-updates/main | + | xenial | aws.generic | FIPS | fips | ubuntu-fips | fips | https://esm.ubuntu.com/fips/ubuntu xenial/main | + | bionic | azure.generic | FIPS | fips | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | + | bionic | azure.generic | FIPS Updates | fips-updates | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | + | bionic | aws.generic | FIPS | fips | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | + | bionic | aws.generic | FIPS Updates | fips-updates | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | + | bionic | gcp.generic | FIPS | fips | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | + | bionic | gcp.generic | FIPS Updates | fips-updates | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | + | focal | azure.generic | FIPS | fips | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | + | focal | azure.generic | FIPS Updates | fips-updates | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | + | focal | aws.generic | FIPS | fips | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | + | focal | aws.generic | FIPS Updates | fips-updates | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | + | focal | gcp.generic | FIPS | fips | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | + | focal | gcp.generic | FIPS Updates | fips-updates | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | + | jammy | azure.generic | FIPS Preview | fips-preview | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips-preview/ubuntu jammy/main | + | jammy | azure.generic | FIPS Updates | fips-updates | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips-updates/ubuntu jammy-updates/main | + | jammy | aws.generic | FIPS Preview | fips-preview | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips-preview/ubuntu jammy/main | + | jammy | aws.generic | FIPS Updates | fips-updates | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips-updates/ubuntu jammy-updates/main | + | jammy | gcp.generic | FIPS Preview | fips-preview | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips-preview/ubuntu jammy/main | + | jammy | gcp.generic | FIPS Updates | fips-updates | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips-updates/ubuntu jammy-updates/main | + + @slow + Scenario Outline: Attached enable of FIPS in an ubuntu image with cloud-init disabled + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `touch /etc/cloud/cloud-init.disabled` with sudo + And I reboot the machine + And I verify that running `cloud-id` `with sudo` exits `1` + Then stderr matches regexp: + """ + File not found '/run/cloud-init/instance-data.json'. Provide a path to instance data json file using --instance-data + """ + When I attach `contract_token` with sudo + And I run `pro enable fips --assume-yes` with sudo + Then stdout contains substring: + """ + Could not determine cloud, defaulting to generic FIPS package. + Updating FIPS package lists + Installing FIPS packages + Updating standard Ubuntu package lists + FIPS enabled + A reboot is required to complete install. + """ + When I run `apt-cache policy ubuntu-fips` as non-root + Then stdout does not match regexp: + """ + .*Installed: \(none\) + """ + When I reboot the machine + And I run `uname -r` as non-root + Then stdout does not match regexp: + """ + aws-fips + """ + And stdout matches regexp: + """ + fips + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | aws.generic | + + @slow + Scenario Outline: Attached enable of FIPS in an ubuntu image with cloud-init disabled + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `touch /etc/cloud/cloud-init.disabled` with sudo + And I reboot the machine + And I verify that running `cloud-id` `with sudo` exits `2` + Then I will see the following on stdout: + """ + disabled + """ + When I attach `contract_token` with sudo + And I run `pro enable fips --assume-yes` with sudo + Then stdout matches regexp: + """ + Could not determine cloud, defaulting to generic FIPS package. + Updating FIPS package lists + Installing FIPS packages + Updating standard Ubuntu package lists + FIPS enabled + A reboot is required to complete install. + """ + When I run `apt-cache policy ubuntu-fips` as non-root + Then stdout does not match regexp: + """ + .*Installed: \(none\) + """ + When I reboot the machine + And I run `uname -r` as non-root + Then stdout does not match regexp: + """ + aws-fips + """ + And stdout matches regexp: + """ + fips + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ + + Examples: ubuntu release + | release | machine_type | + | bionic | aws.generic | + | focal | aws.generic | + + Scenario Outline: Attached enable of FIPS in an ubuntu GCP vm + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I verify that running `pro enable fips-updates --assume-yes` `with sudo` exits `1` + Then stdout matches regexp: + """ + FIPS Updates is not available for Ubuntu 22.04 LTS \(Jammy Jellyfish\) + """ + When I run `pro status --all` with sudo + Then stdout matches regexp: + """ + fips-updates +yes +n/a + """ + + Examples: ubuntu release + | release | machine_type | + | jammy | aws.generic | + | jammy | azure.generic | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/enable_fips_container.feature ubuntu-advantage-tools-32~16.04/features/enable_fips_container.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/enable_fips_container.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/enable_fips_container.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,118 +1,116 @@ @uses.config.contract_token Feature: FIPS enablement in lxd containers - Scenario Outline: Attached enable of FIPS in an ubuntu lxd container - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I apt install `openssh-client openssh-server strongswan openssl libgcrypt20` - And I run `pro enable fips` `with sudo` and stdin `y` - Then stdout matches regexp: - """ - Warning: Enabling in a container. - This will install the FIPS packages but not the kernel. - This container must run on a host with enabled to be - compliant. - Warning: This action can take some time and cannot be undone. - """ - And stdout contains substring: - """ - Updating package lists - Installing packages - Updating standard Ubuntu package lists - enabled - A reboot is required to complete install. - Please run `apt upgrade` to ensure all FIPS packages are updated to the correct - version. - """ - And I verify that `fips` is enabled - When I run `pro status --all` with sudo - Then stdout matches regexp: - """ - FIPS support requires system reboot to complete configuration - """ - And I ensure apt update runs without errors - And I verify that `openssh-server` is installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` - And I verify that `openssh-client` is installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` - And I verify that `strongswan` is installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` - And I verify that `strongswan-hmac` is installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` - And I verify that `openssl` is installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` - And I verify that `` is installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` - And I verify that `-hmac` is installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` - And I verify that `` are installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` - When I reboot the machine - When I run `pro status --all` with sudo - Then stdout does not match regexp: - """ - FIPS support requires system reboot to complete configuration - """ - When I run `pro disable fips` `with sudo` and stdin `y` - Then stdout matches regexp: - """ - This will disable the entitlement but the packages will remain installed. - """ - And stdout matches regexp: - """ - Updating package lists - """ - And stdout does not match regexp: - """ - A reboot is required to complete disable operation - """ - And I verify that `fips` is disabled - When I run `pro status --all` with sudo - Then stdout does not match regexp: - """ - Disabling requires system reboot to complete operation - """ - When I run `apt-cache policy ubuntu-fips` as non-root - Then stdout does not match regexp: - """ - .*Installed: \(none\) - """ - Then I verify that `openssh-server` installed version matches regexp `fips` - And I verify that `openssh-client` installed version matches regexp `fips` - And I verify that `strongswan` installed version matches regexp `fips` - And I verify that `strongswan-hmac` installed version matches regexp `fips` - And I verify that `openssl` installed version matches regexp `fips` - And I verify that `` installed version matches regexp `fips` - And I verify that `-hmac` installed version matches regexp `fips` - And I verify that packages `` installed versions match regexp `fips` + Scenario Outline: Attached enable of FIPS in an ubuntu lxd container + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I apt install `openssh-client openssh-server strongswan openssl libgcrypt20` + And I run `pro enable fips` `with sudo` and stdin `y\ny` + Then stdout matches regexp: + """ + Warning: Enabling in a container. + This will install the FIPS packages but not the kernel. + This container must run on a host with enabled to be + compliant. + Warning: This action can take some time and cannot be undone. + """ + And stdout contains substring: + """ + Installing packages + enabled + A reboot is required to complete install. + Please run `apt upgrade` to ensure all FIPS packages are updated to the correct + version. + """ + And I verify that `fips` is enabled + When I run `pro status --all` with sudo + Then stdout matches regexp: + """ + FIPS support requires system reboot to complete configuration + """ + And I ensure apt update runs without errors + And I verify that `openssh-server` is installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` + And I verify that `openssh-client` is installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` + And I verify that `strongswan` is installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` + And I verify that `strongswan-hmac` is installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` + And I verify that `openssl` is installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` + And I verify that `` is installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` + And I verify that `-hmac` is installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` + And I verify that `` are installed from apt source `https://esm.ubuntu.com/fips/ubuntu /main` + When I reboot the machine + When I run `pro status --all` with sudo + Then stdout does not match regexp: + """ + FIPS support requires system reboot to complete configuration + """ + When I run `pro disable fips` `with sudo` and stdin `y` + Then stdout matches regexp: + """ + This will disable the entitlement but the packages will remain installed. + """ + And stdout matches regexp: + """ + Updating package lists + """ + And stdout does not match regexp: + """ + A reboot is required to complete disable operation + """ + And I verify that `fips` is disabled + When I run `pro status --all` with sudo + Then stdout does not match regexp: + """ + Disabling requires system reboot to complete operation + """ + When I run `apt-cache policy ubuntu-fips` as non-root + Then stdout does not match regexp: + """ + .*Installed: \(none\) + """ + Then I verify that `openssh-server` installed version matches regexp `fips` + And I verify that `openssh-client` installed version matches regexp `fips` + And I verify that `strongswan` installed version matches regexp `fips` + And I verify that `strongswan-hmac` installed version matches regexp `fips` + And I verify that `openssl` installed version matches regexp `fips` + And I verify that `` installed version matches regexp `fips` + And I verify that `-hmac` installed version matches regexp `fips` + And I verify that packages `` installed versions match regexp `fips` - Examples: ubuntu release - | release | machine_type | fips-name | updates | libssl | additional-fips-packages | - | xenial | lxd-container | FIPS | | libssl1.0.0 | openssh-server-hmac openssh-client-hmac | - | xenial | lxd-container | FIPS Updates | -updates | libssl1.0.0 | openssh-server-hmac openssh-client-hmac | - | bionic | lxd-container | FIPS | | libssl1.1 | openssh-server-hmac openssh-client-hmac libgcrypt20 libgcrypt20-hmac | - | bionic | lxd-container | FIPS Updates | -updates | libssl1.1 | openssh-server-hmac openssh-client-hmac libgcrypt20 libgcrypt20-hmac | - | focal | lxd-container | FIPS | | libssl1.1 | libgcrypt20 libgcrypt20-hmac | - | focal | lxd-container | FIPS Updates | -updates | libssl1.1 | libgcrypt20 libgcrypt20-hmac | + Examples: ubuntu release + | release | machine_type | fips-name | updates | libssl | additional-fips-packages | + | xenial | lxd-container | FIPS | | libssl1.0.0 | openssh-server-hmac openssh-client-hmac | + | xenial | lxd-container | FIPS Updates | -updates | libssl1.0.0 | openssh-server-hmac openssh-client-hmac | + | bionic | lxd-container | FIPS | | libssl1.1 | openssh-server-hmac openssh-client-hmac libgcrypt20 libgcrypt20-hmac | + | bionic | lxd-container | FIPS Updates | -updates | libssl1.1 | openssh-server-hmac openssh-client-hmac libgcrypt20 libgcrypt20-hmac | + | focal | lxd-container | FIPS | | libssl1.1 | libgcrypt20 libgcrypt20-hmac | + | focal | lxd-container | FIPS Updates | -updates | libssl1.1 | libgcrypt20 libgcrypt20-hmac | - Scenario Outline: Try to enable FIPS after FIPS Updates in a lxd container - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that `fips-updates` is disabled - And I verify that `fips` is disabled - When I run `pro enable fips-updates --assume-yes` with sudo - Then I verify that `fips-updates` is enabled - When I run `pro status --all` with sudo - Then stdout matches regexp: - """ - fips +yes +n/a - """ - When I verify that running `pro enable fips --assume-yes` `with sudo` exits `1` - Then stdout matches regexp: - """ - Cannot enable FIPS when FIPS Updates is enabled. - """ - When I run `pro status --all` with sudo - Then stdout matches regexp: - """ - fips +yes +n/a - """ - And I verify that `fips-updates` is enabled + Scenario Outline: Try to enable FIPS after FIPS Updates in a lxd container + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that `fips-updates` is disabled + And I verify that `fips` is disabled + When I run `pro enable fips-updates --assume-yes` with sudo + Then I verify that `fips-updates` is enabled + When I run `pro status --all` with sudo + Then stdout matches regexp: + """ + fips +yes +n/a + """ + When I verify that running `pro enable fips --assume-yes` `with sudo` exits `1` + Then stdout matches regexp: + """ + Cannot enable FIPS when FIPS Updates is enabled. + """ + When I run `pro status --all` with sudo + Then stdout matches regexp: + """ + fips +yes +n/a + """ + And I verify that `fips-updates` is enabled - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/enable_fips_pro.feature ubuntu-advantage-tools-32~16.04/features/enable_fips_pro.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/enable_fips_pro.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/enable_fips_pro.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,56 +1,60 @@ Feature: FIPS enablement in PRO cloud based machines - @slow - Scenario Outline: Attached enable of FIPS in an ubuntu Aws PRO vm - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - log_level: debug - """ - And I run `pro auto-attach` with sudo - Then I verify that `fips` is disabled - And I verify that `fips-updates` is disabled - When I run `pro enable --assume-yes` with sudo - Then stdout contains substring: - """ - Updating package lists - Installing packages - Updating standard Ubuntu package lists - enabled - A reboot is required to complete install - """ - And I verify that `` is enabled - And I ensure apt update runs without errors - And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1` - When I run `apt-cache policy ` as non-root - Then stdout does not match regexp: - """ - .*Installed: \(none\) - """ - When I reboot the machine - And I run `uname -r` as non-root - Then stdout matches regexp: - """ - - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ + @slow + Scenario Outline: Attached enable of FIPS in an ubuntu Aws PRO vm + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + log_level: debug + """ + And I run `pro auto-attach` with sudo + Then I verify that `fips` is disabled + And I verify that `fips-updates` is disabled + When I run `pro enable --assume-yes` with sudo + Then stdout matches regexp: + """ + Updating package lists + Installing packages + This will downgrade the kernel from .+ to .+\. + Warning: Downgrading the kernel may cause hardware failures. Please ensure the + hardware is compatible with the new kernel version before proceeding. - Examples: ubuntu release - | release | machine_type | fips-name | fips-service | package-name | kernel-name | fips-apt-source | - | bionic | aws.pro | FIPS | fips | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | - | bionic | aws.pro | FIPS Updates | fips-updates | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | - | bionic | azure.pro | FIPS | fips | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | - | bionic | azure.pro | FIPS Updates | fips-updates | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | - | bionic | gcp.pro | FIPS | fips | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | - | bionic | gcp.pro | FIPS Updates | fips-updates | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | - | focal | aws.pro | FIPS | fips | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | - | focal | aws.pro | FIPS Updates | fips-updates | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | - | focal | azure.pro | FIPS | fips | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | - | focal | azure.pro | FIPS Updates | fips-updates | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | - | focal | gcp.pro | FIPS | fips | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | - | focal | gcp.pro | FIPS Updates | fips-updates | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | + Updating standard Ubuntu package lists(\n.*)? + enabled + A reboot is required to complete install + """ + And I verify that `` is enabled + And I ensure apt update runs without errors + And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1` + When I run `apt-cache policy ` as non-root + Then stdout does not match regexp: + """ + .*Installed: \(none\) + """ + When I reboot the machine + And I run `uname -r` as non-root + Then stdout matches regexp: + """ + + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ + + Examples: ubuntu release + | release | machine_type | fips-name | fips-service | package-name | kernel-name | fips-apt-source | + | bionic | aws.pro | FIPS | fips | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | + | bionic | aws.pro | FIPS Updates | fips-updates | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | + | bionic | azure.pro | FIPS | fips | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | + | bionic | azure.pro | FIPS Updates | fips-updates | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | + | bionic | gcp.pro | FIPS | fips | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | + | bionic | gcp.pro | FIPS Updates | fips-updates | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | + | focal | aws.pro | FIPS | fips | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | + | focal | aws.pro | FIPS Updates | fips-updates | ubuntu-aws-fips | aws-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | + | focal | azure.pro | FIPS | fips | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | + | focal | azure.pro | FIPS Updates | fips-updates | ubuntu-azure-fips | azure-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | + | focal | gcp.pro | FIPS | fips | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | + | focal | gcp.pro | FIPS Updates | fips-updates | ubuntu-gcp-fips | gcp-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/enable_fips_vm.feature ubuntu-advantage-tools-32~16.04/features/enable_fips_vm.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/enable_fips_vm.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/enable_fips_vm.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,493 +1,493 @@ @uses.config.contract_token Feature: FIPS enablement in lxd VMs - @slow - Scenario Outline: Attached enable of FIPS in an ubuntu lxd vm - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - When I run `pro status --format json` with sudo - Then stdout contains substring - """ - {"available": "yes", "blocked_by": [{"name": "livepatch", "reason": "Livepatch cannot be enabled while running the official FIPS certified kernel. If you would like a FIPS compliant kernel with additional bug fixes and security updates, you can use the FIPS Updates service with Livepatch.", "reason_code": "livepatch-invalidates-fips"}], "description": "NIST-certified FIPS crypto packages", "description_override": null, "entitled": "yes", "name": "fips", "status": "disabled", "status_details": "FIPS is not configured", "warning": null} - """ - When I run `pro disable livepatch` with sudo - And I apt install `openssh-client openssh-server strongswan` - And I run `apt-mark hold openssh-client openssh-server strongswan` with sudo - And I run `pro enable ` `with sudo` and stdin `y` - Then stdout matches regexp: - """ - This will install the FIPS packages. The Livepatch service will be unavailable. - Warning: This action can take some time and cannot be undone. - """ - And stdout contains substring: - """ - Updating package lists - Installing packages - Updating standard Ubuntu package lists - enabled - A reboot is required to complete install. - """ - When I run `pro status --all` with sudo - Then stdout matches regexp: - """ - FIPS support requires system reboot to complete configuration - """ - And I ensure apt update runs without errors - And I verify that `openssh-server` is installed from apt source `` - And I verify that `openssh-client` is installed from apt source `` - And I verify that `strongswan` is installed from apt source `` - And I verify that `openssh-server-hmac` is installed from apt source `` - And I verify that `openssh-client-hmac` is installed from apt source `` - And I verify that `strongswan-hmac` is installed from apt source `` - When I run `pro status --format json --all` with sudo - Then stdout contains substring: - """ - {"available": "no", "blocked_by": [{"name": "fips", "reason": "Livepatch cannot be enabled while running the official FIPS certified kernel. If you would like a FIPS compliant kernel with additional bug fixes and security updates, you can use the FIPS Updates service with Livepatch.", "reason_code": "livepatch-invalidates-fips"}], "description": "Canonical Livepatch service", "description_override": null, "entitled": "yes", "name": "livepatch", "status": "n/a", "status_details": "Cannot enable Livepatch when FIPS is enabled.", "warning": null} - """ - When I reboot the machine - And I run `uname -r` as non-root - Then stdout matches regexp: - """ - fips - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ - When I run `pro status --all` with sudo - Then stdout does not match regexp: - """ - FIPS support requires system reboot to complete configuration - """ - When I run `pro disable ` `with sudo` and stdin `y` - Then stdout matches regexp: - """ - This will disable the FIPS entitlement but the FIPS packages will remain installed. - """ - And stdout matches regexp: - """ - Updating package lists - A reboot is required to complete disable operation - """ - When I run `pro status --all` with sudo - Then stdout matches regexp: - """ - Disabling FIPS requires system reboot to complete operation - """ - When I run `apt-cache policy ubuntu-fips` as non-root - Then stdout matches regexp: - """ - .*Installed: \(none\) - """ - When I reboot the machine - Then I verify that `openssh-server` installed version matches regexp `fips` - And I verify that `openssh-client` installed version matches regexp `fips` - And I verify that `strongswan` installed version matches regexp `fips` - And I verify that `openssh-server-hmac` installed version matches regexp `fips` - And I verify that `openssh-client-hmac` installed version matches regexp `fips` - And I verify that `strongswan-hmac` installed version matches regexp `fips` - When I run `apt-mark unhold openssh-client openssh-server strongswan` with sudo - Then I will see the following on stdout: - """ - openssh-client was already not hold. - openssh-server was already not hold. - strongswan was already not hold. - """ - And I verify that `` is disabled - When I run `pro status --all` with sudo - Then stdout does not match regexp: - """ - Disabling FIPS requires system reboot to complete operation - """ - When I run `pro enable --assume-yes --format json --assume-yes` with sudo - Then stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": true, "processed_services": [""], "result": "success", "warnings": []} - """ - When I reboot the machine - And I run `pro disable --assume-yes --format json` with sudo - Then stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": true, "processed_services": [""], "result": "success", "warnings": []} - """ - And I verify that `` is disabled - - Examples: ubuntu release - | release | machine_type | fips-name | fips-service |fips-apt-source | - | xenial | lxd-vm | FIPS | fips |https://esm.ubuntu.com/fips/ubuntu xenial/main | - | bionic | lxd-vm | FIPS | fips |https://esm.ubuntu.com/fips/ubuntu bionic/main | - - @slow - Scenario Outline: Attached enable of FIPS-updates in an ubuntu lxd vm - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `pro disable livepatch` with sudo - And I apt install `openssh-client openssh-server strongswan` - When I run `pro enable ` `with sudo` and stdin `y` - Then stdout matches regexp: - """ - This will install the FIPS packages including security updates. - Warning: This action can take some time and cannot be undone. - """ - And stdout contains substring: - """ - Updating package lists - Installing packages - Updating standard Ubuntu package lists - enabled - A reboot is required to complete install. - """ - And I verify that `` is enabled - And I ensure apt update runs without errors - And I verify that `openssh-server` is installed from apt source `` - And I verify that `openssh-client` is installed from apt source `` - And I verify that `strongswan` is installed from apt source `` - And I verify that `openssh-server-hmac` is installed from apt source `` - And I verify that `openssh-client-hmac` is installed from apt source `` - And I verify that `strongswan-hmac` is installed from apt source `` - When I run `pro status --all --format json` with sudo - Then stdout contains substring: - """ - {"available": "no", "blocked_by": [{"name": "fips-updates", "reason": "FIPS cannot be enabled if FIPS Updates has ever been enabled because FIPS Updates installs security patches that aren't officially certified.", "reason_code": "fips-updates-invalidates-fips"}], "description": "NIST-certified FIPS crypto packages", "description_override": null, "entitled": "yes", "name": "fips", "status": "n/a", "status_details": "Cannot enable FIPS when FIPS Updates is enabled.", "warning": null} - """ - When I reboot the machine - And I run `uname -r` as non-root - Then stdout matches regexp: - """ - fips - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ - When I run `pro disable ` `with sudo` and stdin `y` - Then stdout matches regexp: - """ - This will disable the FIPS Updates entitlement but the FIPS Updates packages will remain installed. - """ - And stdout matches regexp: - """ - Updating package lists - A reboot is required to complete disable operation - """ - When I reboot the machine - Then I verify that `openssh-server` installed version matches regexp `fips` - And I verify that `openssh-client` installed version matches regexp `fips` - And I verify that `strongswan` installed version matches regexp `fips` - And I verify that `openssh-server-hmac` installed version matches regexp `fips` - And I verify that `openssh-client-hmac` installed version matches regexp `fips` - And I verify that `strongswan-hmac` installed version matches regexp `fips` - When I run `apt-mark unhold openssh-client openssh-server strongswan` with sudo - Then I will see the following on stdout: - """ - openssh-client was already not hold. - openssh-server was already not hold. - strongswan was already not hold. - """ - And I verify that `` is disabled - When I verify that running `pro enable fips --assume-yes` `with sudo` exits `1` - Then stdout matches regexp: - """ - Cannot enable FIPS because FIPS Updates was once enabled. - """ - And I verify that files exist matching `/var/lib/ubuntu-advantage/services-once-enabled` - When I run `pro enable --assume-yes` with sudo - And I reboot the machine - Then I verify that `` is enabled - And I verify that `livepatch` is disabled - When I run `pro enable livepatch --assume-yes` with sudo - Then I verify that `` is enabled - And I verify that `livepatch` is enabled - When I run `pro status --all --format json` with sudo - Then stdout contains substring: - """ - {"available": "no", "blocked_by": [{"name": "livepatch", "reason": "Livepatch cannot be enabled while running the official FIPS certified kernel. If you would like a FIPS compliant kernel with additional bug fixes and security updates, you can use the FIPS Updates service with Livepatch.", "reason_code": "livepatch-invalidates-fips"}, {"name": "fips-updates", "reason": "FIPS cannot be enabled if FIPS Updates has ever been enabled because FIPS Updates installs security patches that aren't officially certified.", "reason_code": "fips-updates-invalidates-fips"}], "description": "NIST-certified FIPS crypto packages", "description_override": null, "entitled": "yes", "name": "fips", "status": "n/a", "status_details": "Cannot enable FIPS when FIPS Updates is enabled.", "warning": null} - """ - When I run `pro disable --assume-yes` with sudo - And I run `pro enable --assume-yes --format json --assume-yes` with sudo - Then stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": true, "processed_services": [""], "result": "success", "warnings": []} - """ - When I reboot the machine - And I run `pro disable --assume-yes --format json` with sudo - Then stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": true, "processed_services": [""], "result": "success", "warnings": []} - """ - And I verify that `` is disabled - - Examples: ubuntu release - | release | machine_type | fips-name | fips-service |fips-apt-source | - | xenial | lxd-vm | FIPS Updates | fips-updates |https://esm.ubuntu.com/fips-updates/ubuntu xenial-updates/main | - | bionic | lxd-vm | FIPS Updates | fips-updates |https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | - - @slow - Scenario Outline: Attached enable FIPS-updates while livepatch is enabled - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that `fips-updates` is disabled - And I verify that `livepatch` is enabled - When I run `pro enable fips-updates --assume-yes` with sudo - Then stdout contains substring: - """ - Updating FIPS Updates package lists - Installing FIPS Updates packages - Updating standard Ubuntu package lists - FIPS Updates enabled - A reboot is required to complete install. - """ - And I verify that `fips-updates` is enabled - And I verify that `livepatch` is enabled - When I reboot the machine - And I run `uname -r` as non-root - Then stdout matches regexp: - """ - fips - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ - And I verify that `fips-updates` is enabled - And I verify that `livepatch` is enabled - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-vm | - | bionic | lxd-vm | - - @slow - Scenario Outline: Attached enable of FIPS in an ubuntu lxd vm - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I apt install `openssh-client openssh-server strongswan` - When I run `pro enable --assume-yes` with sudo - Then stdout contains substring: - """ - Updating package lists - Installing packages - Updating standard Ubuntu package lists - enabled - A reboot is required to complete install. - """ - And I verify that `` is enabled - And I ensure apt update runs without errors - And I verify that `openssh-server` is installed from apt source `` - And I verify that `openssh-client` is installed from apt source `` - And I verify that `strongswan` is installed from apt source `` - And I verify that `strongswan-hmac` is installed from apt source `` - When I reboot the machine - And I run `uname -r` as non-root - Then stdout matches regexp: - """ - fips - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ - When I run `pro disable --assume-yes` with sudo - Then stdout matches regexp: - """ - Updating package lists - A reboot is required to complete disable operation - """ - When I reboot the machine - Then I verify that `openssh-server` installed version matches regexp `fips` - And I verify that `openssh-client` installed version matches regexp `fips` - And I verify that `strongswan` installed version matches regexp `fips` - And I verify that `strongswan-hmac` installed version matches regexp `fips` - When I run `apt-mark unhold openssh-client openssh-server strongswan` with sudo - Then I will see the following on stdout: - """ - openssh-client was already not hold. - openssh-server was already not hold. - strongswan was already not hold. - """ - And I verify that `` is disabled - - Examples: ubuntu release - | release | machine_type | fips-name | fips-service |fips-apt-source | - | focal | lxd-vm | FIPS | fips |https://esm.ubuntu.com/fips/ubuntu focal/main | - - @slow - Scenario Outline: Attached enable of FIPS-updates in an ubuntu lxd vm - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I apt install `openssh-client openssh-server strongswan` - When I run `pro enable --assume-yes` with sudo - Then stdout contains substring: - """ - Updating package lists - Installing packages - Updating standard Ubuntu package lists - enabled - A reboot is required to complete install. - """ - And I verify that `` is enabled - And I ensure apt update runs without errors - And I verify that `openssh-server` is installed from apt source `` - And I verify that `openssh-client` is installed from apt source `` - And I verify that `strongswan` is installed from apt source `` - And I verify that `strongswan-hmac` is installed from apt source `` - When I reboot the machine - And I run `uname -r` as non-root - Then stdout matches regexp: - """ - fips - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ - When I run `pro disable --assume-yes` with sudo - Then stdout matches regexp: - """ - Updating package lists - A reboot is required to complete disable operation - """ - When I reboot the machine - Then I verify that `openssh-server` installed version matches regexp `` - And I verify that `openssh-client` installed version matches regexp `` - And I verify that `strongswan` installed version matches regexp `` - And I verify that `strongswan-hmac` installed version matches regexp `` - When I run `apt-mark unhold openssh-client openssh-server strongswan` with sudo - Then stdout matches regexp: - """ - openssh-client was already (not|not on) hold. - openssh-server was already (not|not on) hold. - strongswan was already (not|not on) hold. - """ - And I verify that `` is disabled - When I verify that running `pro enable fips --assume-yes` `with sudo` exits `1` - Then stdout matches regexp: - """ - Cannot enable FIPS because FIPS Updates was once enabled. - """ - And I verify that files exist matching `/var/lib/ubuntu-advantage/services-once-enabled` - - Examples: ubuntu release - | release | machine_type | fips-name | fips-service | fips-package-str | fips-apt-source | - | focal | lxd-vm | FIPS Updates | fips-updates | fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | - | jammy | lxd-vm | FIPS Updates | fips-updates | Fips | https://esm.ubuntu.com/fips-updates/ubuntu jammy-updates/main | - - @slow - Scenario Outline: Attached enable fips-updates on fips enabled vm - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `pro enable fips --assume-yes` with sudo - Then stdout contains substring: - """ - Updating FIPS package lists - Installing FIPS packages - Updating standard Ubuntu package lists - FIPS enabled - A reboot is required to complete install. - """ - And I verify that `fips` is enabled - And I verify that `livepatch` is disabled - When I reboot the machine - And I run `uname -r` as non-root - Then stdout matches regexp: - """ - fips - """ - When I verify that running `pro enable fips-updates --assume-yes` `with sudo` exits `0` - Then stdout contains substring: - """ - One moment, checking your subscription first - Disabling incompatible service: FIPS - Updating FIPS Updates package lists - Installing FIPS Updates packages - Updating standard Ubuntu package lists - FIPS Updates enabled - A reboot is required to complete install. - """ - And I verify that `fips-updates` is enabled - And I verify that `fips` is disabled - When I reboot the machine - And I run `pro enable livepatch` with sudo - Then I verify that `fips-updates` is enabled - And I verify that `fips` is disabled - And I verify that `livepatch` is enabled - When I run `pro status --all` with sudo - Then stdout matches regexp: - """ - fips +yes +n/a - """ - When I run `uname -r` as non-root - Then stdout matches regexp: - """ - fips - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-vm | - | bionic | lxd-vm | - | focal | lxd-vm | - - @slow - Scenario Outline: FIPS enablement message when cloud init didn't run properly - Given a `` `` machine with ubuntu-advantage-tools installed - When I delete the file `/run/cloud-init/instance-data.json` - And I attach `contract_token` with sudo - And I run `pro enable fips --assume-yes` with sudo - Then stdout matches regexp: - """ - Could not determine cloud, defaulting to generic FIPS package. - """ - And I verify that `fips` is enabled - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-vm | - | bionic | lxd-vm | - | focal | lxd-vm | - - @slow - Scenario Outline: Attached enable fips-preview - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - Then I verify that `fips-preview` is disabled - When I verify that running `pro enable fips-preview` `with sudo` and stdin `N` exits `1` - Then stdout matches regexp: - """ - FIPS Preview cannot be enabled with Livepatch. - """ - When I run `pro disable livepatch` with sudo - And I verify that running `pro enable fips-preview` `with sudo` and stdin `N` exits `1` - Then stdout matches regexp: - """ - This will install crypto packages that have been submitted to NIST for review - but do not have FIPS certification yet. Use this for early access to the FIPS - modules. - Please note that the Livepatch service will be unavailable after - this operation. - Warning: This action can take some time and cannot be undone. - """ - When I run `pro enable realtime-kernel --assume-yes` with sudo - And I verify that running `pro enable fips-preview` `with sudo` and stdin `N` exits `1` - Then stdout matches regexp: - """ - FIPS Preview cannot be enabled with Real-time kernel. - """ - - Examples: ubuntu release - | release | machine_type | - | jammy | lxd-vm | + @slow + Scenario Outline: Attached enable of FIPS in an ubuntu lxd vm + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + When I run `pro status --format json` with sudo + Then stdout contains substring + """ + {"available": "yes", "blocked_by": [{"name": "livepatch", "reason": "Livepatch cannot be enabled while running the official FIPS certified kernel. If you would like a FIPS compliant kernel with additional bug fixes and security updates, you can use the FIPS Updates service with Livepatch.", "reason_code": "livepatch-invalidates-fips"}], "description": "NIST-certified FIPS crypto packages", "description_override": null, "entitled": "yes", "name": "fips", "status": "disabled", "status_details": "FIPS is not configured", "warning": null} + """ + When I run `pro disable livepatch` with sudo + And I apt install `openssh-client openssh-server strongswan` + And I run `apt-mark hold openssh-client openssh-server strongswan` with sudo + And I run `pro enable ` `with sudo` and stdin `y` + Then stdout matches regexp: + """ + This will install the FIPS packages. The Livepatch service will be unavailable. + Warning: This action can take some time and cannot be undone. + """ + And stdout contains substring: + """ + Updating package lists + Installing packages + Updating standard Ubuntu package lists + enabled + A reboot is required to complete install. + """ + When I run `pro status --all` with sudo + Then stdout matches regexp: + """ + FIPS support requires system reboot to complete configuration + """ + And I ensure apt update runs without errors + And I verify that `openssh-server` is installed from apt source `` + And I verify that `openssh-client` is installed from apt source `` + And I verify that `strongswan` is installed from apt source `` + And I verify that `openssh-server-hmac` is installed from apt source `` + And I verify that `openssh-client-hmac` is installed from apt source `` + And I verify that `strongswan-hmac` is installed from apt source `` + When I run `pro status --format json --all` with sudo + Then stdout contains substring: + """ + {"available": "no", "blocked_by": [{"name": "fips", "reason": "Livepatch cannot be enabled while running the official FIPS certified kernel. If you would like a FIPS compliant kernel with additional bug fixes and security updates, you can use the FIPS Updates service with Livepatch.", "reason_code": "livepatch-invalidates-fips"}], "description": "Canonical Livepatch service", "description_override": null, "entitled": "yes", "name": "livepatch", "status": "n/a", "status_details": "Cannot enable Livepatch when FIPS is enabled.", "warning": null} + """ + When I reboot the machine + And I run `uname -r` as non-root + Then stdout matches regexp: + """ + fips + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ + When I run `pro status --all` with sudo + Then stdout does not match regexp: + """ + FIPS support requires system reboot to complete configuration + """ + When I run `pro disable ` `with sudo` and stdin `y` + Then stdout matches regexp: + """ + This will disable the FIPS entitlement but the FIPS packages will remain installed. + """ + And stdout matches regexp: + """ + Updating package lists + A reboot is required to complete disable operation + """ + When I run `pro status --all` with sudo + Then stdout matches regexp: + """ + Disabling FIPS requires system reboot to complete operation + """ + When I run `apt-cache policy ubuntu-fips` as non-root + Then stdout matches regexp: + """ + .*Installed: \(none\) + """ + When I reboot the machine + Then I verify that `openssh-server` installed version matches regexp `fips` + And I verify that `openssh-client` installed version matches regexp `fips` + And I verify that `strongswan` installed version matches regexp `fips` + And I verify that `openssh-server-hmac` installed version matches regexp `fips` + And I verify that `openssh-client-hmac` installed version matches regexp `fips` + And I verify that `strongswan-hmac` installed version matches regexp `fips` + When I run `apt-mark unhold openssh-client openssh-server strongswan` with sudo + Then I will see the following on stdout: + """ + openssh-client was already not hold. + openssh-server was already not hold. + strongswan was already not hold. + """ + And I verify that `` is disabled + When I run `pro status --all` with sudo + Then stdout does not match regexp: + """ + Disabling FIPS requires system reboot to complete operation + """ + When I run `pro enable --assume-yes --format json --assume-yes` with sudo + Then stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": true, "processed_services": [""], "result": "success", "warnings": []} + """ + When I reboot the machine + And I run `pro disable --assume-yes --format json` with sudo + Then stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": true, "processed_services": [""], "result": "success", "warnings": []} + """ + And I verify that `` is disabled + + Examples: ubuntu release + | release | machine_type | fips-name | fips-service | fips-apt-source | + | xenial | lxd-vm | FIPS | fips | https://esm.ubuntu.com/fips/ubuntu xenial/main | + | bionic | lxd-vm | FIPS | fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | + + @slow + Scenario Outline: Attached enable of FIPS-updates in an ubuntu lxd vm + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `pro disable livepatch` with sudo + And I apt install `openssh-client openssh-server strongswan` + When I run `pro enable ` `with sudo` and stdin `y` + Then stdout matches regexp: + """ + This will install the FIPS packages including security updates. + Warning: This action can take some time and cannot be undone. + """ + And stdout contains substring: + """ + Updating package lists + Installing packages + Updating standard Ubuntu package lists + enabled + A reboot is required to complete install. + """ + And I verify that `` is enabled + And I ensure apt update runs without errors + And I verify that `openssh-server` is installed from apt source `` + And I verify that `openssh-client` is installed from apt source `` + And I verify that `strongswan` is installed from apt source `` + And I verify that `openssh-server-hmac` is installed from apt source `` + And I verify that `openssh-client-hmac` is installed from apt source `` + And I verify that `strongswan-hmac` is installed from apt source `` + When I run `pro status --all --format json` with sudo + Then stdout contains substring: + """ + {"available": "no", "blocked_by": [{"name": "fips-updates", "reason": "FIPS cannot be enabled if FIPS Updates has ever been enabled because FIPS Updates installs security patches that aren't officially certified.", "reason_code": "fips-updates-invalidates-fips"}], "description": "NIST-certified FIPS crypto packages", "description_override": null, "entitled": "yes", "name": "fips", "status": "n/a", "status_details": "Cannot enable FIPS when FIPS Updates is enabled.", "warning": null} + """ + When I reboot the machine + And I run `uname -r` as non-root + Then stdout matches regexp: + """ + fips + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ + When I run `pro disable ` `with sudo` and stdin `y` + Then stdout matches regexp: + """ + This will disable the FIPS Updates entitlement but the FIPS Updates packages will remain installed. + """ + And stdout matches regexp: + """ + Updating package lists + A reboot is required to complete disable operation + """ + When I reboot the machine + Then I verify that `openssh-server` installed version matches regexp `fips` + And I verify that `openssh-client` installed version matches regexp `fips` + And I verify that `strongswan` installed version matches regexp `fips` + And I verify that `openssh-server-hmac` installed version matches regexp `fips` + And I verify that `openssh-client-hmac` installed version matches regexp `fips` + And I verify that `strongswan-hmac` installed version matches regexp `fips` + When I run `apt-mark unhold openssh-client openssh-server strongswan` with sudo + Then I will see the following on stdout: + """ + openssh-client was already not hold. + openssh-server was already not hold. + strongswan was already not hold. + """ + And I verify that `` is disabled + When I verify that running `pro enable fips --assume-yes` `with sudo` exits `1` + Then stdout matches regexp: + """ + Cannot enable FIPS because FIPS Updates was once enabled. + """ + And I verify that files exist matching `/var/lib/ubuntu-advantage/services-once-enabled` + When I run `pro enable --assume-yes` with sudo + And I reboot the machine + Then I verify that `` is enabled + And I verify that `livepatch` is disabled + When I run `pro enable livepatch --assume-yes` with sudo + Then I verify that `` is enabled + And I verify that `livepatch` is enabled + When I run `pro status --all --format json` with sudo + Then stdout contains substring: + """ + {"available": "no", "blocked_by": [{"name": "livepatch", "reason": "Livepatch cannot be enabled while running the official FIPS certified kernel. If you would like a FIPS compliant kernel with additional bug fixes and security updates, you can use the FIPS Updates service with Livepatch.", "reason_code": "livepatch-invalidates-fips"}, {"name": "fips-updates", "reason": "FIPS cannot be enabled if FIPS Updates has ever been enabled because FIPS Updates installs security patches that aren't officially certified.", "reason_code": "fips-updates-invalidates-fips"}], "description": "NIST-certified FIPS crypto packages", "description_override": null, "entitled": "yes", "name": "fips", "status": "n/a", "status_details": "Cannot enable FIPS when FIPS Updates is enabled.", "warning": null} + """ + When I run `pro disable --assume-yes` with sudo + And I run `pro enable --assume-yes --format json --assume-yes` with sudo + Then stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": true, "processed_services": [""], "result": "success", "warnings": []} + """ + When I reboot the machine + And I run `pro disable --assume-yes --format json` with sudo + Then stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": true, "processed_services": [""], "result": "success", "warnings": []} + """ + And I verify that `` is disabled + + Examples: ubuntu release + | release | machine_type | fips-name | fips-service | fips-apt-source | + | xenial | lxd-vm | FIPS Updates | fips-updates | https://esm.ubuntu.com/fips-updates/ubuntu xenial-updates/main | + | bionic | lxd-vm | FIPS Updates | fips-updates | https://esm.ubuntu.com/fips-updates/ubuntu bionic-updates/main | + + @slow + Scenario Outline: Attached enable FIPS-updates while livepatch is enabled + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that `fips-updates` is disabled + And I verify that `livepatch` is enabled + When I run `pro enable fips-updates --assume-yes` with sudo + Then stdout contains substring: + """ + Updating FIPS Updates package lists + Installing FIPS Updates packages + Updating standard Ubuntu package lists + FIPS Updates enabled + A reboot is required to complete install. + """ + And I verify that `fips-updates` is enabled + And I verify that `livepatch` is enabled + When I reboot the machine + And I run `uname -r` as non-root + Then stdout matches regexp: + """ + fips + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ + And I verify that `fips-updates` is enabled + And I verify that `livepatch` is enabled + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-vm | + | bionic | lxd-vm | + + @slow + Scenario Outline: Attached enable of FIPS in an ubuntu lxd vm + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I apt install `openssh-client openssh-server strongswan` + When I run `pro enable --assume-yes` with sudo + Then stdout contains substring: + """ + Updating package lists + Installing packages + Updating standard Ubuntu package lists + enabled + A reboot is required to complete install. + """ + And I verify that `` is enabled + And I ensure apt update runs without errors + And I verify that `openssh-server` is installed from apt source `` + And I verify that `openssh-client` is installed from apt source `` + And I verify that `strongswan` is installed from apt source `` + And I verify that `strongswan-hmac` is installed from apt source `` + When I reboot the machine + And I run `uname -r` as non-root + Then stdout matches regexp: + """ + fips + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ + When I run `pro disable --assume-yes` with sudo + Then stdout matches regexp: + """ + Updating package lists + A reboot is required to complete disable operation + """ + When I reboot the machine + Then I verify that `openssh-server` installed version matches regexp `fips` + And I verify that `openssh-client` installed version matches regexp `fips` + And I verify that `strongswan` installed version matches regexp `fips` + And I verify that `strongswan-hmac` installed version matches regexp `fips` + When I run `apt-mark unhold openssh-client openssh-server strongswan` with sudo + Then I will see the following on stdout: + """ + openssh-client was already not hold. + openssh-server was already not hold. + strongswan was already not hold. + """ + And I verify that `` is disabled + + Examples: ubuntu release + | release | machine_type | fips-name | fips-service | fips-apt-source | + | focal | lxd-vm | FIPS | fips | https://esm.ubuntu.com/fips/ubuntu focal/main | + + @slow + Scenario Outline: Attached enable of FIPS-updates in an ubuntu lxd vm + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I apt install `openssh-client openssh-server strongswan` + When I run `pro enable --assume-yes` with sudo + Then stdout contains substring: + """ + Updating package lists + Installing packages + Updating standard Ubuntu package lists + enabled + A reboot is required to complete install. + """ + And I verify that `` is enabled + And I ensure apt update runs without errors + And I verify that `openssh-server` is installed from apt source `` + And I verify that `openssh-client` is installed from apt source `` + And I verify that `strongswan` is installed from apt source `` + And I verify that `strongswan-hmac` is installed from apt source `` + When I reboot the machine + And I run `uname -r` as non-root + Then stdout matches regexp: + """ + fips + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ + When I run `pro disable --assume-yes` with sudo + Then stdout matches regexp: + """ + Updating package lists + A reboot is required to complete disable operation + """ + When I reboot the machine + Then I verify that `openssh-server` installed version matches regexp `` + And I verify that `openssh-client` installed version matches regexp `` + And I verify that `strongswan` installed version matches regexp `` + And I verify that `strongswan-hmac` installed version matches regexp `` + When I run `apt-mark unhold openssh-client openssh-server strongswan` with sudo + Then stdout matches regexp: + """ + openssh-client was already (not|not on) hold. + openssh-server was already (not|not on) hold. + strongswan was already (not|not on) hold. + """ + And I verify that `` is disabled + When I verify that running `pro enable fips --assume-yes` `with sudo` exits `1` + Then stdout matches regexp: + """ + Cannot enable FIPS because FIPS Updates was once enabled. + """ + And I verify that files exist matching `/var/lib/ubuntu-advantage/services-once-enabled` + + Examples: ubuntu release + | release | machine_type | fips-name | fips-service | fips-package-str | fips-apt-source | + | focal | lxd-vm | FIPS Updates | fips-updates | fips | https://esm.ubuntu.com/fips-updates/ubuntu focal-updates/main | + | jammy | lxd-vm | FIPS Updates | fips-updates | Fips | https://esm.ubuntu.com/fips-updates/ubuntu jammy-updates/main | + + @slow + Scenario Outline: Attached enable fips-updates on fips enabled vm + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `pro enable fips --assume-yes` with sudo + Then stdout contains substring: + """ + Updating FIPS package lists + Installing FIPS packages + Updating standard Ubuntu package lists + FIPS enabled + A reboot is required to complete install. + """ + And I verify that `fips` is enabled + And I verify that `livepatch` is disabled + When I reboot the machine + And I run `uname -r` as non-root + Then stdout matches regexp: + """ + fips + """ + When I verify that running `pro enable fips-updates --assume-yes` `with sudo` exits `0` + Then stdout contains substring: + """ + One moment, checking your subscription first + Disabling incompatible service: FIPS + Updating FIPS Updates package lists + Installing FIPS Updates packages + Updating standard Ubuntu package lists + FIPS Updates enabled + A reboot is required to complete install. + """ + And I verify that `fips-updates` is enabled + And I verify that `fips` is disabled + When I reboot the machine + And I run `pro enable livepatch` with sudo + Then I verify that `fips-updates` is enabled + And I verify that `fips` is disabled + And I verify that `livepatch` is enabled + When I run `pro status --all` with sudo + Then stdout matches regexp: + """ + fips +yes +n/a + """ + When I run `uname -r` as non-root + Then stdout matches regexp: + """ + fips + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-vm | + | bionic | lxd-vm | + | focal | lxd-vm | + + @slow + Scenario Outline: FIPS enablement message when cloud init didn't run properly + Given a `` `` machine with ubuntu-advantage-tools installed + When I delete the file `/run/cloud-init/instance-data.json` + And I attach `contract_token` with sudo + And I run `pro enable fips --assume-yes` with sudo + Then stdout matches regexp: + """ + Could not determine cloud, defaulting to generic FIPS package. + """ + And I verify that `fips` is enabled + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-vm | + | bionic | lxd-vm | + | focal | lxd-vm | + + @slow + Scenario Outline: Attached enable fips-preview + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that `fips-preview` is disabled + When I verify that running `pro enable fips-preview` `with sudo` and stdin `N` exits `1` + Then stdout matches regexp: + """ + FIPS Preview cannot be enabled with Livepatch. + """ + When I run `pro disable livepatch` with sudo + And I verify that running `pro enable fips-preview` `with sudo` and stdin `N` exits `1` + Then stdout matches regexp: + """ + This will install crypto packages that have been submitted to NIST for review + but do not have FIPS certification yet. Use this for early access to the FIPS + modules. + Please note that the Livepatch service will be unavailable after + this operation. + Warning: This action can take some time and cannot be undone. + """ + When I run `pro enable realtime-kernel --assume-yes` with sudo + And I verify that running `pro enable fips-preview` `with sudo` and stdin `N` exits `1` + Then stdout matches regexp: + """ + FIPS Preview cannot be enabled with Real-time kernel. + """ + + Examples: ubuntu release + | release | machine_type | + | jammy | lxd-vm | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/environment.py ubuntu-advantage-tools-32~16.04/features/environment.py --- ubuntu-advantage-tools-31.2.3~16.04/features/environment.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/environment.py 2024-05-10 17:07:05.000000000 +0000 @@ -8,14 +8,18 @@ import tarfile from typing import Dict, List, Optional, Tuple, Union # noqa: F401 +import paramiko import pycloudlib # type: ignore # noqa: F401 +import yaml from behave.model import Feature, Scenario +from behave.model_core import Status from behave.runner import Context import features.cloud as cloud from features.util import ( BUILDER_NAME_PREFIX, SUT, + UA_TMP_DIR, InstallationSource, landscape_reject_all_pending_computers, process_template_vars, @@ -64,11 +68,13 @@ """ prefix = "UACLIENT_BEHAVE_" + file_config_path = "./features/config/protest.yaml" # These variables are used in .from_environ() to convert the string # environment variable input to the appropriate Python types for use within # the test framework boolean_options = [ + "collect_coverage", "destroy_instances", "ephemeral_instance", "snapshot_strategy", @@ -79,6 +85,9 @@ "contract_token", "contract_token_staging", "contract_token_staging_expired", + "contract_token_staging_expired_sometimes", + "contract_staging_service_account_username", + "contract_staging_service_account_password", "landscape_registration_key", "landscape_api_access_key", "landscape_api_secret_key", @@ -93,11 +102,16 @@ "userdata_file", "check_version", "sbuild_chroot", + "wsl_pubkey_path", + "wsl_privkey_path", + "wsl_ip_address", ] redact_options = [ "contract_token", "contract_token_staging", "contract_token_staging_expired", + "contract_token_staging_expired_sometimes", + "contract_staging_service_account_password", "landscape_registration_key", "landscape_api_access_key", "landscape_api_secret_key", @@ -111,6 +125,7 @@ self, *, cloud_credentials_path: Optional[str] = None, + collect_coverage: bool = False, destroy_instances: bool = True, ephemeral_instance: bool = False, snapshot_strategy: bool = False, @@ -122,6 +137,9 @@ contract_token: Optional[str] = None, contract_token_staging: Optional[str] = None, contract_token_staging_expired: Optional[str] = None, + contract_token_staging_expired_sometimes: Optional[str] = None, + contract_staging_service_account_username: Optional[str] = None, + contract_staging_service_account_password: Optional[str] = None, landscape_registration_key: Optional[str] = None, landscape_api_access_key: Optional[str] = None, landscape_api_secret_key: Optional[str] = None, @@ -132,15 +150,28 @@ userdata_file: Optional[str] = None, check_version: Optional[str] = None, sbuild_chroot: Optional[str] = None, + wsl_pubkey_path: Optional[str] = None, + wsl_privkey_path: Optional[str] = None, + wsl_ip_address: Optional[str] = None, ) -> None: # First, store the values we've detected self.cloud_credentials_path = cloud_credentials_path + self.collect_coverage = collect_coverage self.ephemeral_instance = ephemeral_instance self.snapshot_strategy = snapshot_strategy self.sbuild_output_to_terminal = sbuild_output_to_terminal self.contract_token = contract_token self.contract_token_staging = contract_token_staging self.contract_token_staging_expired = contract_token_staging_expired + self.contract_token_staging_expired_sometimes = ( + contract_token_staging_expired_sometimes + ) + self.contract_staging_service_account_username = ( + contract_staging_service_account_username + ) + self.contract_staging_service_account_password = ( + contract_staging_service_account_password + ) self.landscape_registration_key = landscape_registration_key self.landscape_api_access_key = landscape_api_access_key self.landscape_api_secret_key = landscape_api_secret_key @@ -154,6 +185,10 @@ self.userdata_file = userdata_file self.check_version = check_version self.sbuild_chroot = sbuild_chroot + self.wsl_pubkey_path = wsl_pubkey_path + self.wsl_privkey_path = wsl_privkey_path + self.wsl_ip_address = wsl_ip_address + self.machine_types = ( machine_types.split(",") if machine_types else None ) @@ -229,12 +264,25 @@ @classmethod def from_environ(cls, config) -> "UAClientBehaveConfig": - """Gather config options from os.environ and return a config object""" + """Gather config options from: + + 1. features/config/protest.yaml + 2. os.environ with prefix UACLIENT_BEHAVE_ + 3. -D command line options + + each of which can override the previous. + """ # First, gather all known options kwargs = ( {} ) # type: Dict[str, Union[str, bool, List, InstallationSource]] + try: + with open(cls.file_config_path) as file_config: + kwargs = yaml.safe_load(file_config) + except FileNotFoundError: + print("No config file found at {}".format(cls.file_config_path)) + for key, value in os.environ.items(): if not key.startswith(cls.prefix): continue @@ -402,8 +450,106 @@ ) +def after_scenario(context, scenario): + """Collect the coverage files after the scenario is run.""" + if context.pro_config.collect_coverage: + cov_dir = os.path.join(context.pro_config.artifact_dir, "coverage") + os.makedirs(cov_dir, exist_ok=True) + + inner_dir = os.path.basename(scenario.filename.replace(".", "_")) + new_artifacts_dir = os.path.join( + cov_dir, + inner_dir, + ) + os.makedirs(new_artifacts_dir, exist_ok=True) + if hasattr(context, "machines") and SUT in context.machines: + try: + scenario_name = ( + os.path.basename(scenario.filename.replace(".", "_")) + + "_" + + str(scenario.line) + ) + cov_filename = ".coverage.{}".format(scenario_name) + tmp_covfile_path = "/tmp/{cov_filename}".format( + cov_filename=cov_filename + ) + context.machines[SUT].instance.execute( + [ + "mv", + "/home/ubuntu/.coverage", + tmp_covfile_path, + ], + use_sudo=True, + ) + context.machines[SUT].instance.execute( + [ + "chmod", + "666", + tmp_covfile_path, + ], + use_sudo=True, + ) + + dest = os.path.join(new_artifacts_dir, cov_filename) + context.machines[SUT].instance.pull_file( + tmp_covfile_path, dest + ) + logging.warning("Done collecting coverage.") + except Exception as e: + logging.error(str(e)) + logging.warning("Failed to collect coverage") + + +def _get_relevant_apparmor_logs(context): + if hasattr(context, "machines") and SUT in context.machines: + sut = context.machines[SUT] + if sut.cloud == "lxd-container": + # get apparmor DENIED messages from the host + with open("/var/log/syslog", "r") as syslog_fd: + syslog_messages = syslog_fd.readlines() + apparmor_denied = [ + msg.strip() + for msg in syslog_messages + if ( + "DENIED" in msg + and "ubuntu_pro_" in msg + and sut.instance.name in msg + ) + ] + return apparmor_denied + else: + # get apparmor DENIED messages from the SUT + os.makedirs(UA_TMP_DIR, exist_ok=True) + syslog_dest = os.path.join( + UA_TMP_DIR, "{}-syslog".format(sut.instance.name) + ) + try: + sut.instance.pull_file("/var/log/syslog", syslog_dest) + except paramiko.ssh_exception.SSHException: + logging.warning( + "Unable to pull syslog. Skipping apparmor log check." + ) + return None + with open(syslog_dest, "r") as syslog_fd: + syslog_messages = syslog_fd.readlines() + apparmor_denied = [ + msg.strip() + for msg in syslog_messages + if ("DENIED" in msg and "ubuntu_pro_" in msg) + ] + return apparmor_denied + return None + + def after_step(context, step): """Collect test artifacts in the event of failure.""" + apparmor_logs = _get_relevant_apparmor_logs(context) + if apparmor_logs: + logging.warning("XXX apparmor DENIED begin") + logging.warning("\n".join(apparmor_logs)) + logging.warning("XXX apparmor DENIED end") + # naughty + step.status = Status.failed if step.status == "failed": logging.warning("STEP FAILED. Collecting logs.") inner_dir = os.path.join( @@ -471,6 +617,10 @@ "Failed to delete instance ssh keys:\n{}".format(str(e)) ) + if context.pro_config.clouds.has("wsl"): + cloud_instance = context.pro_config.clouds.get("wsl") + cloud_instance.stop_windows_machine() + # Builder snapshots don't get an auto-cleanup function, so clean them here builder_snapshots = [ name diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/fix.feature ubuntu-advantage-tools-32~16.04/features/fix.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/fix.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/fix.feature 2024-05-10 17:07:05.000000000 +0000 @@ -1,856 +1,931 @@ Feature: Ua fix command behaviour - Scenario Outline: Useful SSL failure message when there aren't any ca-certs - Given a `` `` machine with ubuntu-advantage-tools installed - When I apt update - When I apt remove `ca-certificates` - When I run `rm -f /etc/ssl/certs/ca-certificates.crt` with sudo - When I verify that running `ua fix CVE-1800-123456` `as non-root` exits `1` - Then stderr matches regexp: - """ - Failed to access URL: https://.* - Cannot verify certificate of server - Please install "ca-certificates" and try again. - """ - When I apt install `ca-certificates` - When I run `mv /etc/ssl/certs /etc/ssl/wronglocation` with sudo - When I verify that running `pro fix CVE-1800-123456` `as non-root` exits `1` - Then stderr matches regexp: - """ - Failed to access URL: https://.* - Cannot verify certificate of server - Please check your openssl configuration. - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - Scenario Outline: Fix command on an unattached machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I apt update - When I verify that running `pro fix CVE-1800-123456` `as non-root` exits `1` - Then I will see the following on stderr: - """ - Error: CVE-1800-123456 not found. - """ - When I verify that running `pro fix USN-12345-12` `as non-root` exits `1` - Then I will see the following on stderr: - """ - Error: USN-12345-12 not found. - """ - When I verify that running `pro fix CVE-12345678-12` `as non-root` exits `1` - Then I will see the following on stderr: - """ - Error: issue "CVE-12345678-12" is not recognized. - Usage: "pro fix CVE-yyyy-nnnn" or "pro fix USN-nnnn" - """ - When I verify that running `pro fix USN-12345678-12` `as non-root` exits `1` - Then I will see the following on stderr: - """ - Error: issue "USN-12345678-12" is not recognized. - Usage: "pro fix CVE-yyyy-nnnn" or "pro fix USN-nnnn" - """ - When I apt install `libawl-php=0.60-1` - And I run `pro fix USN-4539-1` with sudo - Then stdout matches regexp: - """ - USN-4539-1: AWL vulnerability - Associated CVEs: - - https://ubuntu.com/security/CVE-2020-11728 - - Fixing requested USN-4539-1 - 1 affected source package is installed: awl - \(1/1\) awl: - A fix is available in Ubuntu standard updates. - .*\{ apt update && apt install --only-upgrade -y libawl-php \}.* - - .*✔.* USN-4539-1 is resolved. - """ - When I run `pro fix CVE-2020-28196` as non-root - Then stdout matches regexp: - """ - CVE-2020-28196: Kerberos vulnerability - - https://ubuntu.com/security/CVE-2020-28196 - - 1 affected source package is installed: krb5 - \(1/1\) krb5: - A fix is available in Ubuntu standard updates. - The update is already installed. - - .*✔.* CVE-2020-28196 is resolved. - """ - When I run `pro fix CVE-2022-24959` as non-root - Then stdout matches regexp: - """ - CVE-2022-24959: Linux kernel vulnerabilities - - https://ubuntu.com/security/CVE-2022-24959 - - No affected source packages are installed. - - .*✔.* CVE-2022-24959 does not affect your system. - """ - When I apt install `rsync=3.1.3-8 zlib1g=1:1.2.11.dfsg-2ubuntu1` - And I run `pro fix USN-5573-1` with sudo - Then stdout matches regexp: - """ - USN-5573-1: rsync vulnerability - Associated CVEs: - - https://ubuntu.com/security/CVE-2022-37434 - - Fixing requested USN-5573-1 - 1 affected source package is installed: rsync - \(1/1\) rsync: - A fix is available in Ubuntu standard updates. - .*\{ apt update && apt install --only-upgrade -y rsync \}.* - - .*✔.* USN-5573-1 is resolved. - - Found related USNs: - - USN-5570-1 - - USN-5570-2 - - Fixing related USNs: - - USN-5570-1 - No affected source packages are installed. - - .*✔.* USN-5570-1 does not affect your system. - - - USN-5570-2 - 1 affected source package is installed: zlib - \(1/1\) zlib: - A fix is available in Ubuntu standard updates. - .*\{ apt update && apt install --only-upgrade -y zlib1g \}.* - - .*✔.* USN-5570-2 is resolved. - - Summary: - .*✔.* USN-5573-1 \[requested\] is resolved. - .*✔.* USN-5570-1 \[related\] does not affect your system. - .*✔.* USN-5570-2 \[related\] is resolved. - """ - When I run `pro fix USN-5573-1` with sudo - Then stdout matches regexp: - """ - USN-5573-1: rsync vulnerability - Associated CVEs: - - https://ubuntu.com/security/CVE-2022-37434 - - Fixing requested USN-5573-1 - 1 affected source package is installed: rsync - \(1/1\) rsync: - A fix is available in Ubuntu standard updates. - The update is already installed. - - .*✔.* USN-5573-1 is resolved. - - Found related USNs: - - USN-5570-1 - - USN-5570-2 - - Fixing related USNs: - - USN-5570-1 - No affected source packages are installed. - - .*✔.* USN-5570-1 does not affect your system. - - - USN-5570-2 - 1 affected source package is installed: zlib - \(1/1\) zlib: - A fix is available in Ubuntu standard updates. - The update is already installed. - - .*✔.* USN-5570-2 is resolved. - - Summary: - .*✔.* USN-5573-1 \[requested\] is resolved. - .*✔.* USN-5570-1 \[related\] does not affect your system. - .*✔.* USN-5570-2 \[related\] is resolved. - """ - When I run `pro fix USN-5573-1 --no-related` with sudo - Then stdout matches regexp: - """ - USN-5573-1: rsync vulnerability - Associated CVEs: - - https://ubuntu.com/security/CVE-2022-37434 - - Fixing requested USN-5573-1 - 1 affected source package is installed: rsync - \(1/1\) rsync: - A fix is available in Ubuntu standard updates. - The update is already installed. - - .*✔.* USN-5573-1 is resolved. - """ - - Examples: ubuntu release details - | release | machine_type | - | focal | lxd-container | - - @uses.config.contract_token - Scenario Outline: Fix command on an unattached machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I verify that running `pro fix CVE-1800-123456` `as non-root` exits `1` - Then I will see the following on stderr: - """ - Error: CVE-1800-123456 not found. - """ - When I verify that running `pro fix USN-12345-12` `as non-root` exits `1` - Then I will see the following on stderr: - """ - Error: USN-12345-12 not found. - """ - When I apt update - When I apt install `libawl-php` - And I reboot the machine - And I run `pro fix USN-4539-1` as non-root - Then stdout matches regexp: - """ - USN-4539-1: AWL vulnerability - Associated CVEs: - - https://ubuntu.com/security/CVE-2020-11728 - - Fixing requested USN-4539-1 - No affected source packages are installed. - - .*✔.* USN-4539-1 does not affect your system. - """ - When I run `pro fix CVE-2020-15180` as non-root - Then stdout matches regexp: - """ - CVE-2020-15180: MariaDB vulnerabilities - - https://ubuntu.com/security/CVE-2020-15180 - - No affected source packages are installed. - - .*✔.* CVE-2020-15180 does not affect your system. - """ - When I run `pro fix CVE-2020-28196` as non-root - Then stdout matches regexp: - """ - CVE-2020-28196: Kerberos vulnerability - - https://ubuntu.com/security/CVE-2020-28196 - - 1 affected source package is installed: krb5 - \(1/1\) krb5: - A fix is available in Ubuntu standard updates. - The update is already installed. - - .*✔.* CVE-2020-28196 is resolved. - """ - When I apt install `expat=2.1.0-7 swish-e matanza ghostscript` - And I verify that running `pro fix CVE-2017-9233 --dry-run` `as non-root` exits `1` - Then stdout matches regexp: - """ - .*WARNING: The option --dry-run is being used. - No packages will be installed when running this command..* - CVE-2017-9233: Expat vulnerability - - https://ubuntu.com/security/CVE-2017-9233 - - 3 affected source packages are installed: expat, matanza, swish-e - \(1/3, 2/3\) matanza, swish-e: - Ubuntu security engineers are investigating this issue. - \(3/3\) expat: - A fix is available in Ubuntu standard updates. - .*\{ apt update && apt install --only-upgrade -y expat \}.* - - 2 packages are still affected: matanza, swish-e - .*✘.* CVE-2017-9233 is not resolved. - """ - When I verify that running `pro fix CVE-2017-9233` `with sudo` exits `1` - Then stdout matches regexp: - """ - CVE-2017-9233: Expat vulnerability - - https://ubuntu.com/security/CVE-2017-9233 - - 3 affected source packages are installed: expat, matanza, swish-e - \(1/3, 2/3\) matanza, swish-e: - Ubuntu security engineers are investigating this issue. - \(3/3\) expat: - A fix is available in Ubuntu standard updates. - .*\{ apt update && apt install --only-upgrade -y expat \}.* - - 2 packages are still affected: matanza, swish-e - .*✘.* CVE-2017-9233 is not resolved. - """ - When I run `pro fix USN-5079-2 --dry-run` as non-root - Then stdout matches regexp: - """ - .*WARNING: The option --dry-run is being used. - No packages will be installed when running this command..* - USN-5079-2: curl vulnerabilities - Associated CVEs: - - https://ubuntu.com/security/CVE-2021-22946 - - https://ubuntu.com/security/CVE-2021-22947 - - Fixing requested USN-5079-2 - 1 affected source package is installed: curl - \(1/1\) curl: - A fix is available in Ubuntu Pro: ESM Infra. - - .*The machine is not attached to an Ubuntu Pro subscription. - To proceed with the fix, a prompt would ask for a valid Ubuntu Pro token. - \{ pro attach TOKEN \}.* - - .*Ubuntu Pro service: esm-infra is not enabled. - To proceed with the fix, a prompt would ask permission to automatically enable - this service. - \{ pro enable esm-infra \}.* - .*\{ apt update && apt install --only-upgrade -y curl libcurl3-gnutls \}.* - - .*✔.* USN-5079-2 is resolved. - - Found related USNs: - - USN-5079-1 - - Fixing related USNs: - - USN-5079-1 - No affected source packages are installed. - - .*✔.* USN-5079-1 does not affect your system. - - Summary: - .*✔.* USN-5079-2 \[requested\] is resolved. - .*✔.* USN-5079-1 \[related\] does not affect your system. - """ - When I fix `USN-5079-2` by attaching to a subscription with `contract_token_staging_expired` - Then stdout matches regexp - """ - USN-5079-2: curl vulnerabilities - Associated CVEs: - - https://ubuntu.com/security/CVE-2021-22946 - - https://ubuntu.com/security/CVE-2021-22947 - - Fixing requested USN-5079-2 - 1 affected source package is installed: curl - \(1/1\) curl: - A fix is available in Ubuntu Pro: ESM Infra. - The update is not installed because this system is not attached to a - subscription. - - Choose: \[S\]ubscribe at https://ubuntu.com/pro/subscribe \[A\]ttach existing token \[C\]ancel - > Enter your token \(from https://ubuntu.com/pro/dashboard\) to attach this system: - > .*\{ pro attach .*\}.* - Attach denied: - Contract ".*" expired on .* - Visit https://ubuntu.com/pro/dashboard to manage contract tokens. - - 1 package is still affected: curl - .*✘.* USN-5079-2 is not resolved. - """ - When I fix `USN-5079-2` by attaching to a subscription with `contract_token` - Then stdout matches regexp: - """ - USN-5079-2: curl vulnerabilities - Associated CVEs: - - https://ubuntu.com/security/CVE-2021-22946 - - https://ubuntu.com/security/CVE-2021-22947 - - Fixing requested USN-5079-2 - 1 affected source package is installed: curl - \(1/1\) curl: - A fix is available in Ubuntu Pro: ESM Infra. - The update is not installed because this system is not attached to a - subscription. - - Choose: \[S\]ubscribe at https://ubuntu.com/pro/subscribe \[A\]ttach existing token \[C\]ancel - > Enter your token \(from https://ubuntu.com/pro/dashboard\) to attach this system: - > .*\{ pro attach .*\}.* - Updating Ubuntu Pro: ESM Apps package lists - Ubuntu Pro: ESM Apps enabled - Updating Ubuntu Pro: ESM Infra package lists - Ubuntu Pro: ESM Infra enabled - """ - And stdout matches regexp: - """ - .*\{ apt update && apt install --only-upgrade -y curl libcurl3-gnutls \}.* - - .*✔.* USN-5079-2 is resolved. - - Found related USNs: - - USN-5079-1 - - Fixing related USNs: - - USN-5079-1 - No affected source packages are installed. - - .*✔.* USN-5079-1 does not affect your system. - - Summary: - .*✔.* USN-5079-2 \[requested\] is resolved. - .*✔.* USN-5079-1 \[related\] does not affect your system. - """ - When I verify that running `pro fix USN-5051-2` `with sudo` exits `2` - Then stdout matches regexp: - """ - USN-5051-2: OpenSSL vulnerability - Associated CVEs: - - https://ubuntu.com/security/CVE-2021-3712 - - Fixing requested USN-5051-2 - 1 affected source package is installed: openssl - \(1/1\) openssl: - A fix is available in Ubuntu Pro: ESM Infra. - .*\{ apt update && apt install --only-upgrade -y libssl1.0.0 openssl \}.* - - A reboot is required to complete fix operation. - .*✘.* USN-5051-2 is not resolved. - """ - When I run `pro disable esm-infra` with sudo - # Allow esm-cache to be populated - And I run `sleep 5` as non-root - And I apt install `gzip` - And I run `pro fix USN-5378-4 --dry-run` as non-root - Then stdout matches regexp: - """ - .*WARNING: The option --dry-run is being used. - No packages will be installed when running this command..* - USN-5378-4: Gzip vulnerability - Associated CVEs: - - https://ubuntu.com/security/CVE-2022-1271 - - Fixing requested USN-5378-4 - 1 affected source package is installed: gzip - \(1/1\) gzip: - A fix is available in Ubuntu Pro: ESM Infra. - - .*Ubuntu Pro service: esm-infra is not enabled. - To proceed with the fix, a prompt would ask permission to automatically enable - this service. - \{ pro enable esm-infra \}.* - .*\{ apt update && apt install --only-upgrade -y gzip \}.* - - .*✔.* USN-5378-4 is resolved. - - Found related USNs: - - USN-5378-1 - - USN-5378-2 - - USN-5378-3 - - Fixing related USNs: - - USN-5378-1 - No affected source packages are installed. - - .*✔.* USN-5378-1 does not affect your system. - - - USN-5378-2 - No affected source packages are installed. - - .*✔.* USN-5378-2 does not affect your system. - - - USN-5378-3 - 1 affected source package is installed: xz-utils - \(1/1\) xz-utils: - A fix is available in Ubuntu Pro: ESM Infra. - - .*Ubuntu Pro service: esm-infra is not enabled. - To proceed with the fix, a prompt would ask permission to automatically enable - this service. - \{ pro enable esm-infra \}.* - .*\{ apt update && apt install --only-upgrade -y liblzma5 xz-utils \}.* - - .*✔.* USN-5378-3 is resolved. - - Summary: - .*✔.* USN-5378-4 \[requested\] is resolved. - .*✔.* USN-5378-1 \[related\] does not affect your system. - .*✔.* USN-5378-2 \[related\] does not affect your system. - .*✔.* USN-5378-3 \[related\] is resolved. - """ - When I run `pro fix USN-5378-4` `with sudo` and stdin `E` - Then stdout matches regexp: - """ - USN-5378-4: Gzip vulnerability - Associated CVEs: - - https://ubuntu.com/security/CVE-2022-1271 - - Fixing requested USN-5378-4 - 1 affected source package is installed: gzip - \(1/1\) gzip: - A fix is available in Ubuntu Pro: ESM Infra. - The update is not installed because this system does not have - esm-infra enabled. - - Choose: \[E\]nable esm-infra \[C\]ancel - > .*\{ pro enable esm-infra \}.* - Updating Ubuntu Pro: ESM Infra package lists - Ubuntu Pro: ESM Infra enabled - .*\{ apt update && apt install --only-upgrade -y gzip \}.* - - .*✔.* USN-5378-4 is resolved. - - Found related USNs: - - USN-5378-1 - - USN-5378-2 - - USN-5378-3 - - Fixing related USNs: - - USN-5378-1 - No affected source packages are installed. - - .*✔.* USN-5378-1 does not affect your system. - - - USN-5378-2 - No affected source packages are installed. - - .*✔.* USN-5378-2 does not affect your system. - - - USN-5378-3 - 1 affected source package is installed: xz-utils - \(1/1\) xz-utils: - A fix is available in Ubuntu Pro: ESM Infra. - .*\{ apt update && apt install --only-upgrade -y liblzma5 xz-utils \}.* - - .*✔.* USN-5378-3 is resolved. - - Summary: - .*✔.* USN-5378-4 \[requested\] is resolved. - .*✔.* USN-5378-1 \[related\] does not affect your system. - .*✔.* USN-5378-2 \[related\] does not affect your system. - .*✔.* USN-5378-3 \[related\] is resolved. - """ - When I run `pro detach --assume-yes` with sudo - And I run `sed -i "/xenial-updates/d" /etc/apt/sources.list` with sudo - And I run `sed -i "/xenial-security/d" /etc/apt/sources.list` with sudo - And I apt update - And I apt install `squid` - And I verify that running `pro fix CVE-2020-25097` `as non-root` exits `1` - Then stdout matches regexp: - """ - CVE-2020-25097: Squid vulnerabilities - - https://ubuntu.com/security/CVE-2020-25097 - - 1 affected source package is installed: squid3 - \(1/1\) squid3: - A fix is available in Ubuntu standard updates. - - Cannot install package squid version 3.5.12-1ubuntu7.16 - - Cannot install package squid-common version 3.5.12-1ubuntu7.16 - - 1 package is still affected: squid3 - .*✘.* CVE-2020-25097 is not resolved - """ - - Examples: ubuntu release details - | release | machine_type | - | xenial | lxd-container | - - Scenario: Fix command on an unattached machine - Given a `bionic` `lxd-container` machine with ubuntu-advantage-tools installed - When I apt update - When I verify that running `pro fix CVE-1800-123456` `as non-root` exits `1` - Then I will see the following on stderr: - """ - Error: CVE-1800-123456 not found. - """ - When I verify that running `pro fix USN-12345-12` `as non-root` exits `1` - Then I will see the following on stderr: - """ - Error: USN-12345-12 not found. - """ - When I verify that running `pro fix CVE-12345678-12` `as non-root` exits `1` - Then I will see the following on stderr: - """ - Error: issue "CVE-12345678-12" is not recognized. - Usage: "pro fix CVE-yyyy-nnnn" or "pro fix USN-nnnn" - """ - When I verify that running `pro fix USN-12345678-12` `as non-root` exits `1` - Then I will see the following on stderr: - """ - Error: issue "USN-12345678-12" is not recognized. - Usage: "pro fix CVE-yyyy-nnnn" or "pro fix USN-nnnn" - """ - When I apt install `libawl-php` - And I run `pro fix USN-4539-1 --dry-run` as non-root - Then stdout matches regexp: - """ - .*WARNING: The option --dry-run is being used. - No packages will be installed when running this command..* - USN-4539-1: AWL vulnerability - Associated CVEs: - - https://ubuntu.com/security/CVE-2020-11728 - - Fixing requested USN-4539-1 - No affected source packages are installed. - - .*✔.* USN-4539-1 does not affect your system. - """ - When I run `pro fix USN-4539-1` as non-root - Then stdout matches regexp: - """ - USN-4539-1: AWL vulnerability - Associated CVEs: - - https://ubuntu.com/security/CVE-2020-11728 - - Fixing requested USN-4539-1 - No affected source packages are installed. - - .*✔.* USN-4539-1 does not affect your system. - """ - When I run `pro fix CVE-2020-28196` as non-root - Then stdout matches regexp: - """ - CVE-2020-28196: Kerberos vulnerability - - https://ubuntu.com/security/CVE-2020-28196 - - 1 affected source package is installed: krb5 - \(1/1\) krb5: - A fix is available in Ubuntu standard updates. - The update is already installed. - - .*✔.* CVE-2020-28196 is resolved. - """ - When I apt install `xterm=330-1ubuntu2` - And I verify that running `pro fix CVE-2021-27135` `as non-root` exits `1` - Then stdout matches regexp: - """ - CVE-2021-27135: xterm vulnerability - - https://ubuntu.com/security/CVE-2021-27135 - - 1 affected source package is installed: xterm - \(1/1\) xterm: - A fix is available in Ubuntu standard updates. - Package fixes cannot be installed. - To install them, run this command as root \(try using sudo\) - - 1 package is still affected: xterm - .*✘.* CVE-2021-27135 is not resolved. - """ - When I run `pro fix CVE-2021-27135 --dry-run` with sudo - Then stdout matches regexp: - """ - .*WARNING: The option --dry-run is being used. - No packages will be installed when running this command..* - CVE-2021-27135: xterm vulnerability - - https://ubuntu.com/security/CVE-2021-27135 - - 1 affected source package is installed: xterm - \(1/1\) xterm: - A fix is available in Ubuntu standard updates. - .*\{ apt update && apt install --only-upgrade -y xterm \}.* - - .*✔.* CVE-2021-27135 is resolved. - """ - When I run `pro fix CVE-2021-27135` with sudo - Then stdout matches regexp: - """ - CVE-2021-27135: xterm vulnerability - - https://ubuntu.com/security/CVE-2021-27135 - - 1 affected source package is installed: xterm - \(1/1\) xterm: - A fix is available in Ubuntu standard updates. - .*\{ apt update && apt install --only-upgrade -y xterm \}.* - - .*✔.* CVE-2021-27135 is resolved. - """ - When I run `pro fix CVE-2021-27135` with sudo - Then stdout matches regexp: - """ - CVE-2021-27135: xterm vulnerability - - https://ubuntu.com/security/CVE-2021-27135 - - 1 affected source package is installed: xterm - \(1/1\) xterm: - A fix is available in Ubuntu standard updates. - The update is already installed. - - .*✔.* CVE-2021-27135 is resolved. - """ - When I apt install `libbz2-1.0=1.0.6-8.1 bzip2=1.0.6-8.1` - And I run `pro fix USN-4038-3` with sudo - Then stdout matches regexp: - """ - USN-4038-3: bzip2 regression - Found Launchpad bugs: - - https://launchpad.net/bugs/1834494 - - Fixing requested USN-4038-3 - 1 affected source package is installed: bzip2 - \(1/1\) bzip2: - A fix is available in Ubuntu standard updates. - .*\{ apt update && apt install --only-upgrade -y bzip2 libbz2-1.0 \}.* - - .*✔.* USN-4038-3 is resolved. - """ - When I run `pro fix USN-6130-1` as non-root - Then stdout matches regexp: - """ - USN-6130-1: Linux kernel vulnerabilities - Associated CVEs: - - https://ubuntu.com/security/CVE-2023-30456 - - https://ubuntu.com/security/CVE-2023-1380 - - https://ubuntu.com/security/CVE-2023-32233 - - https://ubuntu.com/security/CVE-2023-31436 - - Fixing requested USN-6130-1 - No affected source packages are installed. - - .*✔.* USN-6130-1 does not affect your system. - - Found related USNs: - - USN-6033-1 - - USN-6122-1 - - USN-6123-1 - - USN-6124-1 - - USN-6127-1 - - USN-6131-1 - - USN-6132-1 - - USN-6135-1 - - USN-6149-1 - - USN-6150-1 - - USN-6162-1 - - USN-6173-1 - - USN-6175-1 - - USN-6186-1 - - USN-6222-1 - - USN-6256-1 - - USN-6385-1 - - USN-6460-1 - - Fixing related USNs: - - USN-6033-1 - No affected source packages are installed. - - .*✔.* USN-6033-1 does not affect your system. - - - USN-6122-1 - No affected source packages are installed. - - .*✔.* USN-6122-1 does not affect your system. - - - USN-6123-1 - No affected source packages are installed. - - .*✔.* USN-6123-1 does not affect your system. - - - USN-6124-1 - No affected source packages are installed. - - .*✔.* USN-6124-1 does not affect your system. - - - USN-6127-1 - No affected source packages are installed. - - .*✔.* USN-6127-1 does not affect your system. - - - USN-6131-1 - No affected source packages are installed. - - .*✔.* USN-6131-1 does not affect your system. - - - USN-6132-1 - No affected source packages are installed. - - .*✔.* USN-6132-1 does not affect your system. - - - USN-6135-1 - No affected source packages are installed. - - .*✔.* USN-6135-1 does not affect your system. - - - USN-6149-1 - No affected source packages are installed. - - .*✔.* USN-6149-1 does not affect your system. - - - USN-6150-1 - No affected source packages are installed. - - .*✔.* USN-6150-1 does not affect your system. - - - USN-6162-1 - No affected source packages are installed. - - .*✔.* USN-6162-1 does not affect your system. - - - USN-6173-1 - No affected source packages are installed. - - .*✔.* USN-6173-1 does not affect your system. - - - USN-6175-1 - No affected source packages are installed. - - .*✔.* USN-6175-1 does not affect your system. - - - USN-6186-1 - No affected source packages are installed. - - .*✔.* USN-6186-1 does not affect your system. - - - USN-6222-1 - No affected source packages are installed. - - .*✔.* USN-6222-1 does not affect your system. - - - USN-6256-1 - No affected source packages are installed. - - .*✔.* USN-6256-1 does not affect your system. - - - USN-6385-1 - No affected source packages are installed. - - .*✔.* USN-6385-1 does not affect your system. - - - USN-6460-1 - No affected source packages are installed. - - .*✔.* USN-6460-1 does not affect your system. - - Summary: - .*✔.* USN-6130-1 \[requested\] does not affect your system. - .*✔.* USN-6033-1 \[related\] does not affect your system. - .*✔.* USN-6122-1 \[related\] does not affect your system. - .*✔.* USN-6123-1 \[related\] does not affect your system. - .*✔.* USN-6124-1 \[related\] does not affect your system. - .*✔.* USN-6127-1 \[related\] does not affect your system. - .*✔.* USN-6131-1 \[related\] does not affect your system. - .*✔.* USN-6132-1 \[related\] does not affect your system. - .*✔.* USN-6135-1 \[related\] does not affect your system. - .*✔.* USN-6149-1 \[related\] does not affect your system. - .*✔.* USN-6150-1 \[related\] does not affect your system. - .*✔.* USN-6162-1 \[related\] does not affect your system. - .*✔.* USN-6173-1 \[related\] does not affect your system. - .*✔.* USN-6175-1 \[related\] does not affect your system. - .*✔.* USN-6186-1 \[related\] does not affect your system. - .*✔.* USN-6222-1 \[related\] does not affect your system. - .*✔.* USN-6256-1 \[related\] does not affect your system. - .*✔.* USN-6385-1 \[related\] does not affect your system. - .*✔.* USN-6460-1 \[related\] does not affect your system. - """ - When I run `pro fix CVE-2023-42752` with sudo - Then stdout matches regexp: - """ - CVE-2023-42752: Linux kernel \(NVIDIA\) vulnerabilities - - https://ubuntu.com/security/CVE-2023-42752 - - No affected source packages are installed. - - .*✔.* CVE-2023-42752 does not affect your system. - """ - - Scenario: Fix command on a machine without security/updates source lists - Given a `bionic` `lxd-container` machine with ubuntu-advantage-tools installed - When I run `sed -i "/bionic-updates/d" /etc/apt/sources.list` with sudo - And I run `sed -i "/bionic-security/d" /etc/apt/sources.list` with sudo - And I apt update - And I run `wget -O pkg.deb https://launchpad.net/ubuntu/+source/openssl/1.1.1-1ubuntu2.1~18.04.14/+build/22454675/+files/openssl_1.1.1-1ubuntu2.1~18.04.14_amd64.deb` as non-root - And I run `dpkg -i pkg.deb` with sudo - And I verify that running `pro fix CVE-2023-0286` `as non-root` exits `1` - Then stdout matches regexp: - """ - CVE-2023-0286: OpenSSL vulnerabilities - - https://ubuntu.com/security/CVE-2023-0286 - - 2 affected source packages are installed: openssl, openssl1.0 - \(1/2, 2/2\) openssl, openssl1.0: - A fix is available in Ubuntu standard updates. - - Cannot install package openssl version 1.1.1-1ubuntu2.1~18.04.21 - - 1 package is still affected: openssl - .*✘.* CVE-2023-0286 is not resolved. - """ + Scenario Outline: Useful SSL failure message when there aren't any ca-certs + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt remove `ca-certificates` + When I run `rm -f /etc/ssl/certs/ca-certificates.crt` with sudo + When I verify that running `ua fix CVE-1800-123456` `as non-root` exits `1` + Then stderr matches regexp: + """ + Failed to access URL: https://.* + Cannot verify certificate of server + Please install "ca-certificates" and try again. + """ + When I apt install `ca-certificates` + When I run `mv /etc/ssl/certs /etc/ssl/wronglocation` with sudo + When I verify that running `pro fix CVE-1800-123456` `as non-root` exits `1` + Then stderr matches regexp: + """ + Failed to access URL: https://.* + Cannot verify certificate of server + Please check your openssl configuration. + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: Fix command on an unattached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify that running `pro fix CVE-1800-123456` `as non-root` exits `1` + Then I will see the following on stderr: + """ + Error: CVE-1800-123456 not found. + """ + When I verify that running `pro fix USN-12345-12` `as non-root` exits `1` + Then I will see the following on stderr: + """ + Error: USN-12345-12 not found. + """ + When I verify that running `pro fix CVE-12345678-12` `as non-root` exits `1` + Then I will see the following on stderr: + """ + Error: issue "CVE-12345678-12" is not recognized. + Usage: "pro fix CVE-yyyy-nnnn" or "pro fix USN-nnnn" + """ + When I verify that running `pro fix USN-12345678-12` `as non-root` exits `1` + Then I will see the following on stderr: + """ + Error: issue "USN-12345678-12" is not recognized. + Usage: "pro fix CVE-yyyy-nnnn" or "pro fix USN-nnnn" + """ + When I apt install `libawl-php=0.60-1` + And I run `pro fix USN-4539-1` with sudo + Then stdout matches regexp: + """ + USN-4539-1: AWL vulnerability + Associated CVEs: + - https://ubuntu.com/security/CVE-2020-11728 + + Fixing requested USN-4539-1 + 1 affected source package is installed: awl + \(1/1\) awl: + A fix is available in Ubuntu standard updates. + .*\{ apt update && apt install --only-upgrade -y libawl-php \}.* + + .*✔.* USN-4539-1 is resolved. + """ + When I run `pro fix CVE-2020-28196` as non-root + Then stdout matches regexp: + """ + CVE-2020-28196: Kerberos vulnerability + - https://ubuntu.com/security/CVE-2020-28196 + + 1 affected source package is installed: krb5 + \(1/1\) krb5: + A fix is available in Ubuntu standard updates. + The update is already installed. + + .*✔.* CVE-2020-28196 is resolved. + """ + When I run `pro fix CVE-2022-24959` as non-root + Then stdout matches regexp: + """ + CVE-2022-24959: Linux kernel vulnerabilities + - https://ubuntu.com/security/CVE-2022-24959 + + No affected source packages are installed. + + .*✔.* CVE-2022-24959 does not affect your system. + """ + When I apt install `rsync=3.1.3-8 zlib1g=1:1.2.11.dfsg-2ubuntu1` + And I run `pro fix USN-5573-1` with sudo + Then stdout matches regexp: + """ + USN-5573-1: rsync vulnerability + Associated CVEs: + - https://ubuntu.com/security/CVE-2022-37434 + + Fixing requested USN-5573-1 + 1 affected source package is installed: rsync + \(1/1\) rsync: + A fix is available in Ubuntu standard updates. + .*\{ apt update && apt install --only-upgrade -y rsync \}.* + + .*✔.* USN-5573-1 is resolved. + + Found related USNs: + - USN-5570-1 + - USN-5570-2 + - USN-6736-1 + + Fixing related USNs: + - USN-5570-1 + No affected source packages are installed. + + .*✔.* USN-5570-1 does not affect your system. + + - USN-5570-2 + 1 affected source package is installed: zlib + \(1/1\) zlib: + A fix is available in Ubuntu standard updates. + .*\{ apt update && apt install --only-upgrade -y zlib1g \}.* + + .*✔.* USN-5570-2 is resolved. + + - USN-6736-1 + 1 affected source package is installed: klibc + \(1/1\) klibc: + A fix is available in Ubuntu standard updates. + The update is already installed. + + .*✔.* USN-6736-1 is resolved. + + Summary: + .*✔.* USN-5573-1 \[requested\] is resolved. + .*✔.* USN-5570-1 \[related\] does not affect your system. + .*✔.* USN-5570-2 \[related\] is resolved. + .*✔.* USN-6736-1 \[related\] is resolved. + """ + When I run `pro fix USN-5573-1` with sudo + Then stdout matches regexp: + """ + USN-5573-1: rsync vulnerability + Associated CVEs: + - https://ubuntu.com/security/CVE-2022-37434 + + Fixing requested USN-5573-1 + 1 affected source package is installed: rsync + \(1/1\) rsync: + A fix is available in Ubuntu standard updates. + The update is already installed. + + .*✔.* USN-5573-1 is resolved. + + Found related USNs: + - USN-5570-1 + - USN-5570-2 + - USN-6736-1 + + Fixing related USNs: + - USN-5570-1 + No affected source packages are installed. + + .*✔.* USN-5570-1 does not affect your system. + + - USN-5570-2 + 1 affected source package is installed: zlib + \(1/1\) zlib: + A fix is available in Ubuntu standard updates. + The update is already installed. + + .*✔.* USN-5570-2 is resolved. + + - USN-6736-1 + 1 affected source package is installed: klibc + \(1/1\) klibc: + A fix is available in Ubuntu standard updates. + The update is already installed. + + .*✔.* USN-6736-1 is resolved. + + Summary: + .*✔.* USN-5573-1 \[requested\] is resolved. + .*✔.* USN-5570-1 \[related\] does not affect your system. + .*✔.* USN-5570-2 \[related\] is resolved. + .*✔.* USN-6736-1 \[related\] is resolved. + """ + When I run `pro fix USN-5573-1 --no-related` with sudo + Then stdout matches regexp: + """ + USN-5573-1: rsync vulnerability + Associated CVEs: + - https://ubuntu.com/security/CVE-2022-37434 + + Fixing requested USN-5573-1 + 1 affected source package is installed: rsync + \(1/1\) rsync: + A fix is available in Ubuntu standard updates. + The update is already installed. + + .*✔.* USN-5573-1 is resolved. + """ + + Examples: ubuntu release details + | release | machine_type | + | focal | lxd-container | + | focal | wsl | + + @uses.config.contract_token + Scenario Outline: Fix command on an unattached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify that running `pro fix CVE-1800-123456` `as non-root` exits `1` + Then I will see the following on stderr: + """ + Error: CVE-1800-123456 not found. + """ + When I verify that running `pro fix USN-12345-12` `as non-root` exits `1` + Then I will see the following on stderr: + """ + Error: USN-12345-12 not found. + """ + # Make sure esm cache is empty + # Technically a folder right, but this works + When I delete the file `/var/lib/ubuntu-advantage/apt-esm/` + When I delete the file `/var/lib/apt/periodic/update-success-stamp` + And I verify that running `pro fix USN-5079-2 --dry-run` `as non-root` exits `1` + Then stdout matches regexp: + """ + .*WARNING: The option --dry-run is being used. + No packages will be installed when running this command..* + USN-5079-2: curl vulnerabilities + Associated CVEs: + - https://ubuntu.com/security/CVE-2021-22946 + - https://ubuntu.com/security/CVE-2021-22947 + + Fixing requested USN-5079-2 + 1 affected source package is installed: curl + + .*WARNING: Unable to update ESM cache when running as non-root, + please run sudo apt update and try again if packages cannot be found..* + + \(1/1\) curl: + A fix is available in Ubuntu Pro: ESM Infra. + - Cannot install package curl version .* + - Cannot install package libcurl3-gnutls version .* + + .*The machine is not attached to an Ubuntu Pro subscription. + To proceed with the fix, a prompt would ask for a valid Ubuntu Pro token. + { pro attach TOKEN }.* + + .*Ubuntu Pro service: esm-infra is not enabled. + To proceed with the fix, a prompt would ask permission to automatically enable + this service. + { pro enable esm-infra }.* + + 1 package is still affected: curl + .*USN-5079-2 is not resolved. + """ + When I apt update + # We just need to await for the esm-cache to be populated + And I run `sleep 5` as non-root + And I run `pro fix USN-5079-2 --dry-run` as non-root + Then stdout matches regexp: + """ + .*WARNING: The option --dry-run is being used. + No packages will be installed when running this command..* + USN-5079-2: curl vulnerabilities + Associated CVEs: + - https://ubuntu.com/security/CVE-2021-22946 + - https://ubuntu.com/security/CVE-2021-22947 + + Fixing requested USN-5079-2 + 1 affected source package is installed: curl + \(1/1\) curl: + A fix is available in Ubuntu Pro: ESM Infra. + + .*The machine is not attached to an Ubuntu Pro subscription. + To proceed with the fix, a prompt would ask for a valid Ubuntu Pro token. + \{ pro attach TOKEN \}.* + + .*Ubuntu Pro service: esm-infra is not enabled. + To proceed with the fix, a prompt would ask permission to automatically enable + this service. + \{ pro enable esm-infra \}.* + .*\{ apt update && apt install --only-upgrade -y curl libcurl3-gnutls \}.* + + .*USN-5079-2 is resolved. + + Found related USNs: + - USN-5079-1 + + Fixing related USNs: + - USN-5079-1 + No affected source packages are installed. + + .*USN-5079-1 does not affect your system. + + Summary: + .*USN-5079-2 \[requested\] is resolved. + .*USN-5079-1 \[related\] does not affect your system. + """ + When I apt install `libawl-php` + And I reboot the machine + And I run `pro fix USN-4539-1` as non-root + Then stdout matches regexp: + """ + USN-4539-1: AWL vulnerability + Associated CVEs: + - https://ubuntu.com/security/CVE-2020-11728 + + Fixing requested USN-4539-1 + No affected source packages are installed. + + .*✔.* USN-4539-1 does not affect your system. + """ + When I run `pro fix CVE-2020-15180` as non-root + Then stdout matches regexp: + """ + CVE-2020-15180: MariaDB vulnerabilities + - https://ubuntu.com/security/CVE-2020-15180 + + No affected source packages are installed. + + .*✔.* CVE-2020-15180 does not affect your system. + """ + When I run `pro fix CVE-2020-28196` as non-root + Then stdout matches regexp: + """ + CVE-2020-28196: Kerberos vulnerability + - https://ubuntu.com/security/CVE-2020-28196 + + 1 affected source package is installed: krb5 + \(1/1\) krb5: + A fix is available in Ubuntu standard updates. + The update is already installed. + + .*✔.* CVE-2020-28196 is resolved. + """ + When I apt install `expat=2.1.0-7 swish-e matanza ghostscript` + And I verify that running `pro fix CVE-2017-9233 --dry-run` `as non-root` exits `1` + Then stdout matches regexp: + """ + .*WARNING: The option --dry-run is being used. + No packages will be installed when running this command..* + CVE-2017-9233: Expat vulnerability + - https://ubuntu.com/security/CVE-2017-9233 + + 3 affected source packages are installed: expat, matanza, swish-e + \(1/3, 2/3\) matanza, swish-e: + Ubuntu security engineers are investigating this issue. + \(3/3\) expat: + A fix is available in Ubuntu standard updates. + .*\{ apt update && apt install --only-upgrade -y expat \}.* + + 2 packages are still affected: matanza, swish-e + .*✘.* CVE-2017-9233 is not resolved. + """ + When I verify that running `pro fix CVE-2017-9233` `with sudo` exits `1` + Then stdout matches regexp: + """ + CVE-2017-9233: Expat vulnerability + - https://ubuntu.com/security/CVE-2017-9233 + + 3 affected source packages are installed: expat, matanza, swish-e + \(1/3, 2/3\) matanza, swish-e: + Ubuntu security engineers are investigating this issue. + \(3/3\) expat: + A fix is available in Ubuntu standard updates. + .*\{ apt update && apt install --only-upgrade -y expat \}.* + + 2 packages are still affected: matanza, swish-e + .*✘.* CVE-2017-9233 is not resolved. + """ + When I fix `USN-5079-2` by attaching to a subscription with `contract_token_staging_expired` + Then stdout matches regexp + """ + USN-5079-2: curl vulnerabilities + Associated CVEs: + - https://ubuntu.com/security/CVE-2021-22946 + - https://ubuntu.com/security/CVE-2021-22947 + + Fixing requested USN-5079-2 + 1 affected source package is installed: curl + \(1/1\) curl: + A fix is available in Ubuntu Pro: ESM Infra. + The update is not installed because this system is not attached to a + subscription. + + Choose: \[S\]ubscribe at https://ubuntu.com/pro/subscribe \[A\]ttach existing token \[C\]ancel + > Enter your token \(from https://ubuntu.com/pro/dashboard\) to attach this system: + > .*\{ pro attach .*\}.* + Attach denied: + Contract ".*" expired on .* + Visit https://ubuntu.com/pro/dashboard to manage contract tokens. + + 1 package is still affected: curl + .*✘.* USN-5079-2 is not resolved. + """ + When I fix `USN-5079-2` by attaching to a subscription with `contract_token` + Then stdout matches regexp: + """ + USN-5079-2: curl vulnerabilities + Associated CVEs: + - https://ubuntu.com/security/CVE-2021-22946 + - https://ubuntu.com/security/CVE-2021-22947 + + Fixing requested USN-5079-2 + 1 affected source package is installed: curl + \(1/1\) curl: + A fix is available in Ubuntu Pro: ESM Infra. + The update is not installed because this system is not attached to a + subscription. + + Choose: \[S\]ubscribe at https://ubuntu.com/pro/subscribe \[A\]ttach existing token \[C\]ancel + > Enter your token \(from https://ubuntu.com/pro/dashboard\) to attach this system: + > .*\{ pro attach .*\}.* + """ + And stdout matches regexp: + """ + .*\{ apt update && apt install --only-upgrade -y curl libcurl3-gnutls \}.* + + .*✔.* USN-5079-2 is resolved. + + Found related USNs: + - USN-5079-1 + + Fixing related USNs: + - USN-5079-1 + No affected source packages are installed. + + .*✔.* USN-5079-1 does not affect your system. + + Summary: + .*✔.* USN-5079-2 \[requested\] is resolved. + .*✔.* USN-5079-1 \[related\] does not affect your system. + """ + When I verify that running `pro fix USN-5051-2` `with sudo` exits `2` + Then stdout matches regexp: + """ + USN-5051-2: OpenSSL vulnerability + Associated CVEs: + - https://ubuntu.com/security/CVE-2021-3712 + + Fixing requested USN-5051-2 + 1 affected source package is installed: openssl + \(1/1\) openssl: + A fix is available in Ubuntu Pro: ESM Infra. + .*\{ apt update && apt install --only-upgrade -y libssl1.0.0 openssl \}.* + + A reboot is required to complete fix operation. + .*✘.* USN-5051-2 is not resolved. + """ + When I run `pro disable esm-infra` with sudo + # Allow esm-cache to be populated + And I run `sleep 5` as non-root + And I apt install `gzip` + And I run `pro fix USN-5378-4 --dry-run` as non-root + Then stdout matches regexp: + """ + .*WARNING: The option --dry-run is being used. + No packages will be installed when running this command..* + USN-5378-4: Gzip vulnerability + Associated CVEs: + - https://ubuntu.com/security/CVE-2022-1271 + + Fixing requested USN-5378-4 + 1 affected source package is installed: gzip + \(1/1\) gzip: + A fix is available in Ubuntu Pro: ESM Infra. + + .*Ubuntu Pro service: esm-infra is not enabled. + To proceed with the fix, a prompt would ask permission to automatically enable + this service. + \{ pro enable esm-infra \}.* + .*\{ apt update && apt install --only-upgrade -y gzip \}.* + + .*✔.* USN-5378-4 is resolved. + + Found related USNs: + - USN-5378-1 + - USN-5378-2 + - USN-5378-3 + + Fixing related USNs: + - USN-5378-1 + No affected source packages are installed. + + .*✔.* USN-5378-1 does not affect your system. + + - USN-5378-2 + No affected source packages are installed. + + .*✔.* USN-5378-2 does not affect your system. + + - USN-5378-3 + 1 affected source package is installed: xz-utils + \(1/1\) xz-utils: + A fix is available in Ubuntu Pro: ESM Infra. + + .*Ubuntu Pro service: esm-infra is not enabled. + To proceed with the fix, a prompt would ask permission to automatically enable + this service. + \{ pro enable esm-infra \}.* + .*\{ apt update && apt install --only-upgrade -y liblzma5 xz-utils \}.* + + .*✔.* USN-5378-3 is resolved. + + Summary: + .*✔.* USN-5378-4 \[requested\] is resolved. + .*✔.* USN-5378-1 \[related\] does not affect your system. + .*✔.* USN-5378-2 \[related\] does not affect your system. + .*✔.* USN-5378-3 \[related\] is resolved. + """ + When I run `pro fix USN-5378-4` `with sudo` and stdin `E` + Then stdout matches regexp: + """ + USN-5378-4: Gzip vulnerability + Associated CVEs: + - https://ubuntu.com/security/CVE-2022-1271 + + Fixing requested USN-5378-4 + 1 affected source package is installed: gzip + \(1/1\) gzip: + A fix is available in Ubuntu Pro: ESM Infra. + The update is not installed because this system does not have + esm-infra enabled. + + Choose: \[E\]nable esm-infra \[C\]ancel + > .*\{ pro enable esm-infra \}.* + Enabling Ubuntu Pro: ESM Infra + Ubuntu Pro: ESM Infra enabled + .*\{ apt update && apt install --only-upgrade -y gzip \}.* + + .*✔.* USN-5378-4 is resolved. + + Found related USNs: + - USN-5378-1 + - USN-5378-2 + - USN-5378-3 + + Fixing related USNs: + - USN-5378-1 + No affected source packages are installed. + + .*✔.* USN-5378-1 does not affect your system. + + - USN-5378-2 + No affected source packages are installed. + + .*✔.* USN-5378-2 does not affect your system. + + - USN-5378-3 + 1 affected source package is installed: xz-utils + \(1/1\) xz-utils: + A fix is available in Ubuntu Pro: ESM Infra. + .*\{ apt update && apt install --only-upgrade -y liblzma5 xz-utils \}.* + + .*✔.* USN-5378-3 is resolved. + + Summary: + .*✔.* USN-5378-4 \[requested\] is resolved. + .*✔.* USN-5378-1 \[related\] does not affect your system. + .*✔.* USN-5378-2 \[related\] does not affect your system. + .*✔.* USN-5378-3 \[related\] is resolved. + """ + When I run `pro detach --assume-yes` with sudo + And I run `sed -i "/xenial-updates/d" /etc/apt/sources.list` with sudo + And I run `sed -i "/xenial-security/d" /etc/apt/sources.list` with sudo + And I apt update + And I apt install `squid` + And I verify that running `pro fix CVE-2020-25097` `as non-root` exits `1` + Then stdout matches regexp: + """ + CVE-2020-25097: Squid vulnerabilities + - https://ubuntu.com/security/CVE-2020-25097 + + 1 affected source package is installed: squid3 + \(1/1\) squid3: + A fix is available in Ubuntu standard updates. + - Cannot install package squid version 3.5.12-1ubuntu7.16 + - Cannot install package squid-common version 3.5.12-1ubuntu7.16 + + 1 package is still affected: squid3 + .*✘.* CVE-2020-25097 is not resolved + """ + + Examples: ubuntu release details + | release | machine_type | + | xenial | lxd-container | + | xenial | lxd-vm | + + Scenario Outline: Fix command on an unattached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify that running `pro fix CVE-1800-123456` `as non-root` exits `1` + Then I will see the following on stderr: + """ + Error: CVE-1800-123456 not found. + """ + When I verify that running `pro fix USN-12345-12` `as non-root` exits `1` + Then I will see the following on stderr: + """ + Error: USN-12345-12 not found. + """ + When I verify that running `pro fix CVE-12345678-12` `as non-root` exits `1` + Then I will see the following on stderr: + """ + Error: issue "CVE-12345678-12" is not recognized. + Usage: "pro fix CVE-yyyy-nnnn" or "pro fix USN-nnnn" + """ + When I verify that running `pro fix USN-12345678-12` `as non-root` exits `1` + Then I will see the following on stderr: + """ + Error: issue "USN-12345678-12" is not recognized. + Usage: "pro fix CVE-yyyy-nnnn" or "pro fix USN-nnnn" + """ + When I apt install `libawl-php` + And I run `pro fix USN-4539-1 --dry-run` as non-root + Then stdout matches regexp: + """ + .*WARNING: The option --dry-run is being used. + No packages will be installed when running this command..* + USN-4539-1: AWL vulnerability + Associated CVEs: + - https://ubuntu.com/security/CVE-2020-11728 + + Fixing requested USN-4539-1 + No affected source packages are installed. + + .*✔.* USN-4539-1 does not affect your system. + """ + When I run `pro fix USN-4539-1` as non-root + Then stdout matches regexp: + """ + USN-4539-1: AWL vulnerability + Associated CVEs: + - https://ubuntu.com/security/CVE-2020-11728 + + Fixing requested USN-4539-1 + No affected source packages are installed. + + .*✔.* USN-4539-1 does not affect your system. + """ + When I run `pro fix CVE-2020-28196` as non-root + Then stdout matches regexp: + """ + CVE-2020-28196: Kerberos vulnerability + - https://ubuntu.com/security/CVE-2020-28196 + + 1 affected source package is installed: krb5 + \(1/1\) krb5: + A fix is available in Ubuntu standard updates. + The update is already installed. + + .*✔.* CVE-2020-28196 is resolved. + """ + When I apt install `xterm=330-1ubuntu2` + And I verify that running `pro fix CVE-2021-27135` `as non-root` exits `1` + Then stdout matches regexp: + """ + CVE-2021-27135: xterm vulnerability + - https://ubuntu.com/security/CVE-2021-27135 + + 1 affected source package is installed: xterm + \(1/1\) xterm: + A fix is available in Ubuntu standard updates. + Package fixes cannot be installed. + To install them, run this command as root \(try using sudo\) + + 1 package is still affected: xterm + .*✘.* CVE-2021-27135 is not resolved. + """ + When I run `pro fix CVE-2021-27135 --dry-run` with sudo + Then stdout matches regexp: + """ + .*WARNING: The option --dry-run is being used. + No packages will be installed when running this command..* + CVE-2021-27135: xterm vulnerability + - https://ubuntu.com/security/CVE-2021-27135 + + 1 affected source package is installed: xterm + \(1/1\) xterm: + A fix is available in Ubuntu standard updates. + .*\{ apt update && apt install --only-upgrade -y xterm \}.* + + .*✔.* CVE-2021-27135 is resolved. + """ + When I run `pro fix CVE-2021-27135` with sudo + Then stdout matches regexp: + """ + CVE-2021-27135: xterm vulnerability + - https://ubuntu.com/security/CVE-2021-27135 + + 1 affected source package is installed: xterm + \(1/1\) xterm: + A fix is available in Ubuntu standard updates. + .*\{ apt update && apt install --only-upgrade -y xterm \}.* + + .*✔.* CVE-2021-27135 is resolved. + """ + When I run `pro fix CVE-2021-27135` with sudo + Then stdout matches regexp: + """ + CVE-2021-27135: xterm vulnerability + - https://ubuntu.com/security/CVE-2021-27135 + + 1 affected source package is installed: xterm + \(1/1\) xterm: + A fix is available in Ubuntu standard updates. + The update is already installed. + + .*✔.* CVE-2021-27135 is resolved. + """ + When I apt install `libbz2-1.0=1.0.6-8.1 bzip2=1.0.6-8.1` + And I run `pro fix USN-4038-3` with sudo + Then stdout matches regexp: + """ + USN-4038-3: bzip2 regression + Found Launchpad bugs: + - https://launchpad.net/bugs/1834494 + + Fixing requested USN-4038-3 + 1 affected source package is installed: bzip2 + \(1/1\) bzip2: + A fix is available in Ubuntu standard updates. + .*\{ apt update && apt install --only-upgrade -y bzip2 libbz2-1.0 \}.* + + .*✔.* USN-4038-3 is resolved. + """ + When I run `pro fix USN-6130-1` as non-root + Then stdout matches regexp: + """ + USN-6130-1: Linux kernel vulnerabilities + Associated CVEs: + - https://ubuntu.com/security/CVE-2023-30456 + - https://ubuntu.com/security/CVE-2023-1380 + - https://ubuntu.com/security/CVE-2023-32233 + - https://ubuntu.com/security/CVE-2023-31436 + + Fixing requested USN-6130-1 + No affected source packages are installed. + + .*✔.* USN-6130-1 does not affect your system. + + Found related USNs: + - USN-6033-1 + - USN-6122-1 + - USN-6123-1 + - USN-6124-1 + - USN-6127-1 + - USN-6131-1 + - USN-6132-1 + - USN-6135-1 + - USN-6149-1 + - USN-6150-1 + - USN-6162-1 + - USN-6173-1 + - USN-6175-1 + - USN-6186-1 + - USN-6222-1 + - USN-6256-1 + - USN-6385-1 + - USN-6460-1 + - USN-6699-1 + + Fixing related USNs: + - USN-6033-1 + No affected source packages are installed. + + .*✔.* USN-6033-1 does not affect your system. + + - USN-6122-1 + No affected source packages are installed. + + .*✔.* USN-6122-1 does not affect your system. + + - USN-6123-1 + No affected source packages are installed. + + .*✔.* USN-6123-1 does not affect your system. + + - USN-6124-1 + No affected source packages are installed. + + .*✔.* USN-6124-1 does not affect your system. + + - USN-6127-1 + No affected source packages are installed. + + .*✔.* USN-6127-1 does not affect your system. + + - USN-6131-1 + No affected source packages are installed. + + .*✔.* USN-6131-1 does not affect your system. + + - USN-6132-1 + No affected source packages are installed. + + .*✔.* USN-6132-1 does not affect your system. + + - USN-6135-1 + No affected source packages are installed. + + .*✔.* USN-6135-1 does not affect your system. + + - USN-6149-1 + No affected source packages are installed. + + .*✔.* USN-6149-1 does not affect your system. + + - USN-6150-1 + No affected source packages are installed. + + .*✔.* USN-6150-1 does not affect your system. + + - USN-6162-1 + No affected source packages are installed. + + .*✔.* USN-6162-1 does not affect your system. + + - USN-6173-1 + No affected source packages are installed. + + .*✔.* USN-6173-1 does not affect your system. + + - USN-6175-1 + No affected source packages are installed. + + .*✔.* USN-6175-1 does not affect your system. + + - USN-6186-1 + No affected source packages are installed. + + .*✔.* USN-6186-1 does not affect your system. + + - USN-6222-1 + No affected source packages are installed. + + .*✔.* USN-6222-1 does not affect your system. + + - USN-6256-1 + No affected source packages are installed. + + .*✔.* USN-6256-1 does not affect your system. + + - USN-6385-1 + No affected source packages are installed. + + .*✔.* USN-6385-1 does not affect your system. + + - USN-6460-1 + No affected source packages are installed. + + .*✔.* USN-6460-1 does not affect your system. + + - USN-6699-1 + No affected source packages are installed. + + .*✔.* USN-6699-1 does not affect your system. + + Summary: + .*✔.* USN-6130-1 \[requested\] does not affect your system. + .*✔.* USN-6033-1 \[related\] does not affect your system. + .*✔.* USN-6122-1 \[related\] does not affect your system. + .*✔.* USN-6123-1 \[related\] does not affect your system. + .*✔.* USN-6124-1 \[related\] does not affect your system. + .*✔.* USN-6127-1 \[related\] does not affect your system. + .*✔.* USN-6131-1 \[related\] does not affect your system. + .*✔.* USN-6132-1 \[related\] does not affect your system. + .*✔.* USN-6135-1 \[related\] does not affect your system. + .*✔.* USN-6149-1 \[related\] does not affect your system. + .*✔.* USN-6150-1 \[related\] does not affect your system. + .*✔.* USN-6162-1 \[related\] does not affect your system. + .*✔.* USN-6173-1 \[related\] does not affect your system. + .*✔.* USN-6175-1 \[related\] does not affect your system. + .*✔.* USN-6186-1 \[related\] does not affect your system. + .*✔.* USN-6222-1 \[related\] does not affect your system. + .*✔.* USN-6256-1 \[related\] does not affect your system. + .*✔.* USN-6385-1 \[related\] does not affect your system. + .*✔.* USN-6460-1 \[related\] does not affect your system. + .*✔.* USN-6699-1 \[related\] does not affect your system. + """ + When I run `pro fix CVE-2023-42752` with sudo + Then stdout matches regexp: + """ + CVE-2023-42752: Linux kernel \(NVIDIA\) vulnerabilities + - https://ubuntu.com/security/CVE-2023-42752 + + No affected source packages are installed. + + .*✔.* CVE-2023-42752 does not affect your system. + """ + + Examples: ubuntu release details + | release | machine_type | + | bionic | lxd-container | + | bionic | lxd-vm | + | bionic | wsl | + + Scenario Outline: Fix command on a machine without security/updates source lists + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `sed -i "/bionic-updates/d" /etc/apt/sources.list` with sudo + And I run `sed -i "/bionic-security/d" /etc/apt/sources.list` with sudo + And I apt update + And I run `wget -O pkg.deb https://launchpad.net/ubuntu/+source/openssl/1.1.1-1ubuntu2.1~18.04.14/+build/22454675/+files/openssl_1.1.1-1ubuntu2.1~18.04.14_amd64.deb` as non-root + And I run `dpkg -i pkg.deb` with sudo + And I verify that running `pro fix CVE-2023-0286` `as non-root` exits `1` + Then stdout matches regexp: + """ + CVE-2023-0286: OpenSSL vulnerabilities + - https://ubuntu.com/security/CVE-2023-0286 + + 2 affected source packages are installed: openssl, openssl1.0 + \(1/2, 2/2\) openssl, openssl1.0: + A fix is available in Ubuntu standard updates. + - Cannot install package openssl version 1.1.1-1ubuntu2.1~18.04.21 + + 1 package is still affected: openssl + .*✘.* CVE-2023-0286 is not resolved. + """ + + Examples: ubuntu release details + | release | machine_type | + | bionic | lxd-container | + | bionic | lxd-vm | + | bionic | wsl | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/i18n.feature ubuntu-advantage-tools-32~16.04/features/i18n.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/i18n.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/i18n.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,226 +1,229 @@ Feature: Pro supports multiple languages - Scenario Outline: Translation works - Given a `` `` machine with ubuntu-advantage-tools installed - When I run shell command `LANGUAGE=pt_BR.UTF-8 pro security-status` as non-root - Then stdout contains substring: - """ - Esta máquina NÃO está vinculada a uma assinatura do Ubuntu Pro. - """ - When I run shell command `LANGUAGE=pt_BR.UTF-8 pro status --all` as non-root - Then stdout contains substring: - """ - sim - """ - Then stdout contains substring: - """ - não - """ - When I apt update - And I apt install `jq` - And I run shell command `LANGUAGE=pt_BR.UTF-8 pro status --format json | jq .services[0].available` as non-root - Then I will see the following on stdout: - """ - "yes" - """ - When I apt remove `ubuntu-pro-client-l10n` - When I run shell command `LANGUAGE=pt_BR.UTF-8 pro security-status` as non-root - Then stdout contains substring: - """ - This machine is NOT attached to an Ubuntu Pro subscription. - """ - Examples: ubuntu release - | release | machine_type | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - Scenario Outline: Translation works - Given a `` `` machine with ubuntu-advantage-tools installed - When I run shell command `LANGUAGE=pt_BR.UTF-8 pro security-status` as non-root - Then stdout contains substring: - """ - Ubuntu Pro não está disponível para versões do Ubuntu não LTS. - """ - When I run shell command `LANGUAGE=pt_BR.UTF-8 pro status --all` as non-root - Then stdout contains substring: - """ - não - """ - When I apt update - And I apt install `jq` - And I run shell command `LANGUAGE=pt_BR.UTF-8 pro status --format json | jq .result` as non-root - Then I will see the following on stdout: - """ - "success" - """ - When I apt remove `ubuntu-pro-client-l10n` - When I run shell command `LANGUAGE=pt_BR.UTF-8 pro security-status` as non-root - Then stdout contains substring: - """ - Ubuntu Pro is not available for non-LTS releases. - """ - Examples: ubuntu release - | release | machine_type | - | mantic | lxd-container | - - # Note: Translations do work on xenial, but our test environment triggers a bug in python that - # causes it to think we're in an ascii-only environment - Scenario Outline: Translation doesn't error when python thinks it's ascii only - Given a `` `` machine with ubuntu-advantage-tools installed - When I run shell command `env LC_CTYPE=pt_BR.UTF-8 LANGUAGE=pt_BR.UTF-8 python3 -c \"import sys; print(sys.stdout.encoding)\"` as non-root - Then I will see the following on stdout: - """ - ANSI_X3.4-1968 - """ - When I run shell command `env LC_CTYPE=pt_BR.UTF-8 LANGUAGE=pt_BR.UTF-8 pro security-status` as non-root - Then stdout contains substring: - """ - This machine is NOT attached to an Ubuntu Pro subscription. - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - - Scenario Outline: apt-hook translations work - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - When I apt update - When I apt upgrade - When I run `pro detach --assume-yes` with sudo - When I apt update - When I apt install `hello` - When I attach `contract_token` with sudo - # Didn't call the step specifically because of the language environment - When I run shell command `LANGUAGE=pt_BR.UTF-8 apt upgrade -y` with sudo - Then stdout matches regexp: - """ - 1 atualização de segurança do esm-apps - """ - Examples: ubuntu release - | release | machine_type | - | focal | lxd-container | - - @uses.config.contract_token - Scenario Outline: Pro client's commands run successfully in a different locale - Given a `` `` machine with ubuntu-advantage-tools installed - ## Change the locale - When I apt install `language-pack-fr` - And I run `update-locale LANG=fr_FR.UTF-8` with sudo - And I reboot the machine - And I run `cat /etc/default/locale` as non-root - Then stdout matches regexp: - """ - LANG=fr_FR.UTF-8 - """ - #Attach invalid token - When I verify that running `pro attach INVALID_TOKEN` `with sudo` exits `1` - Then stderr matches regexp: - """ - Invalid token. See https://ubuntu.com/pro - """ - When I run `lscpu` as non-root - Then stdout does not match regexp: - """ - Architecture: - """ - When I apt update - Then stdout does not match regexp: - """ - Hit - """ - When I verify that running `pro attach INVALID_TOKEN` `as non-root` exits `1` - Then I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - When I verify that running `pro attach invalid-token --format json` `with sudo` exits `1` - Then stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"message": "Invalid token. See https://ubuntu.com/pro/dashboard", "message_code": "attach-invalid-token", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - When I attach `contract_token` with sudo - # Refresh command - When I run `pro refresh` with sudo - Then I will see the following on stdout: - """ - Successfully processed your pro configuration. - Successfully refreshed your subscription. - Successfully updated Ubuntu Pro related APT and MOTD messages. - """ + Scenario Outline: Translation works + Given a `` `` machine with ubuntu-advantage-tools installed + When I run shell command `LANGUAGE=pt_BR.UTF-8 pro security-status` as non-root + Then stdout contains substring: + """ + Esta máquina NÃO está vinculada a uma assinatura do Ubuntu Pro. + """ + When I run shell command `LANGUAGE=pt_BR.UTF-8 pro status --all` as non-root + Then stdout contains substring: + """ + sim + """ + Then stdout contains substring: + """ + não + """ + When I apt install `jq` + And I run shell command `LANGUAGE=pt_BR.UTF-8 pro status --format json | jq .services[0].available` as non-root + Then I will see the following on stdout: + """ + "yes" + """ + When I apt remove `ubuntu-pro-client-l10n` + When I run shell command `LANGUAGE=pt_BR.UTF-8 pro security-status` as non-root + Then stdout contains substring: + """ + This machine is NOT attached to an Ubuntu Pro subscription. + """ + + Examples: ubuntu release + | release | machine_type | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + Scenario Outline: Translation works + Given a `` `` machine with ubuntu-advantage-tools installed + When I run shell command `LANGUAGE=pt_BR.UTF-8 pro security-status` as non-root + Then stdout contains substring: + """ + Ubuntu Pro não está disponível para versões do Ubuntu não LTS. + """ + When I run shell command `LANGUAGE=pt_BR.UTF-8 pro status --all` as non-root + Then stdout contains substring: + """ + não + """ + When I apt install `jq` + And I run shell command `LANGUAGE=pt_BR.UTF-8 pro status --format json | jq .result` as non-root + Then I will see the following on stdout: + """ + "success" + """ + When I apt remove `ubuntu-pro-client-l10n` + When I run shell command `LANGUAGE=pt_BR.UTF-8 pro security-status` as non-root + Then stdout contains substring: + """ + Ubuntu Pro is not available for non-LTS releases. + """ + + Examples: ubuntu release + | release | machine_type | + | mantic | lxd-container | + + # Note: Translations do work on xenial, but our test environment triggers a bug in python that + # causes it to think we're in an ascii-only environment + Scenario Outline: Translation doesn't error when python thinks it's ascii only + Given a `` `` machine with ubuntu-advantage-tools installed + When I run shell command `env LC_CTYPE=pt_BR.UTF-8 LANGUAGE=pt_BR.UTF-8 python3 -c \"import sys; print(sys.stdout.encoding)\"` as non-root + Then I will see the following on stdout: + """ + ANSI_X3.4-1968 + """ + When I run shell command `env LC_CTYPE=pt_BR.UTF-8 LANGUAGE=pt_BR.UTF-8 pro security-status` as non-root + Then stdout contains substring: + """ + This machine is NOT attached to an Ubuntu Pro subscription. + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + + Scenario Outline: apt-hook translations work + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + When I apt upgrade + When I run `pro detach --assume-yes` with sudo + When I apt update + When I apt install `hello` + When I attach `contract_token` with sudo + # Didn't call the step specifically because of the language environment + When I run shell command `LANGUAGE=pt_BR.UTF-8 apt upgrade -y` with sudo + Then stdout matches regexp: + """ + 1 atualização de segurança do esm-apps + """ + + Examples: ubuntu release + | release | machine_type | + | focal | lxd-container | + + @uses.config.contract_token + Scenario Outline: Pro client's commands run successfully in a different locale + Given a `` `` machine with ubuntu-advantage-tools installed + # # Change the locale + When I apt install `language-pack-fr` + And I run `update-locale LANG=fr_FR.UTF-8` with sudo + And I reboot the machine + And I run `cat /etc/default/locale` as non-root + Then stdout matches regexp: + """ + LANG=fr_FR.UTF-8 + """ + # Attach invalid token + When I verify that running `pro attach INVALID_TOKEN` `with sudo` exits `1` + Then stderr matches regexp: + """ + Invalid token. See https://ubuntu.com/pro + """ + When I run `lscpu` as non-root + Then stdout does not match regexp: + """ + Architecture: + """ + When I apt update + Then stdout does not match regexp: + """ + Hit + """ + When I verify that running `pro attach INVALID_TOKEN` `as non-root` exits `1` + Then I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + When I verify that running `pro attach invalid-token --format json` `with sudo` exits `1` + Then stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"message": "Invalid token. See https://ubuntu.com/pro/dashboard", "message_code": "attach-invalid-token", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + When I attach `contract_token` with sudo + # Refresh command + When I run `pro refresh` with sudo + Then I will see the following on stdout: + """ + Successfully processed your pro configuration. + Successfully refreshed your subscription. + Successfully updated Ubuntu Pro related APT and MOTD messages. + """ # auto-attach command - When I verify that running `pro auto-attach` `with sudo` exits `2` - Then stderr matches regexp: - """ - This machine is already attached to '.+' - To use a different subscription first run: sudo pro detach. - """ - # status command - When I run `pro status --format json` as non-root - Then stdout is a json matching the `ua_status` schema - When I run `pro status --format yaml` as non-root - Then stdout is a yaml matching the `ua_status` schema - When I create the file `/var/lib/ubuntu-advantage/machine-token-overlay.json` with the following: - """ - { - "machineTokenInfo": { - "contractInfo": { - "effectiveTo": null - } - } - } - """ - And I append the following on uaclient config: - """ - features: - machine_token_overlay: "/var/lib/ubuntu-advantage/machine-token-overlay.json" - """ - And I run `pro status` with sudo - Then stdout contains substring: - """ - Valid until: Unknown/Expired - """ - # api command invalid endpoint - When I verify that running `pro api invalid.endpoint` `with sudo` exits `1` - Then stdout matches regexp: - """ - {\"_schema_version\": \"v1\", \"data\": {\"meta\": {\"environment_vars\": \[]}}, \"errors\": \[{\"code\": \"api\-invalid\-endpoint", \"meta\": {\"endpoint\": \"invalid.endpoint\"}, \"title\": \"'invalid\.endpoint' is not a valid endpoint\"}], \"result\": \"failure\", \"version\": \".*\", \"warnings\": \[]} - """ - When I verify that running `pro api u.pro.version.v1 --args extra=arg` `with sudo` exits `1` - Then stdout matches regexp: - """ - {\"_schema_version\": \"v1\", \"data\": {\"meta\": {\"environment_vars\": \[]}}, \"errors\": \[{\"code\": \"api\-no\-argument\-for\-endpoint\", \"meta\": {\"endpoint\": \"u.pro.version.v1\"}, \"title\": \"u\.pro\.version\.v1 accepts no arguments\"}], \"result\": \"failure\", \"version\": \".*\", \"warnings\": \[]} - """ - # api command valid endpoint - When I run `pro api u.pro.version.v1` with sudo - Then stdout matches regexp: - """ - {\"_schema_version\": \"v1\", \"data\": {\"attributes\": {\"installed_version\": \".*\"}, \"meta\": {\"environment_vars\": \[]}, \"type\": \"Version\"}, \"errors\": \[], \"result\": \"success\", \"version\": \".*\", \"warnings\": \[]} - """ - When I run `UA_LOG_FILE=/tmp/some_file OTHER_ENVVAR=not_there pro api u.pro.version.v1` with sudo - Then stdout matches regexp: - """ - {\"_schema_version\": \"v1\", \"data\": {\"attributes\": {\"installed_version\": \".*\"}, \"meta\": {\"environment_vars\": \[{\"name\": \"UA_LOG_FILE\", \"value\": \"\/tmp\/some_file\"}]}, \"type\": \"Version\"}, \"errors\": \[], \"result\": \"success\", \"version\": \".*\", \"warnings\": \[]} - """ - When I run `ua api u.pro.attach.auto.should_auto_attach.v1` with sudo - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"should_auto_attach": false}, "meta": {"environment_vars": \[\]}, "type": "ShouldAutoAttach"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - # version - When I run `pro version` as non-root - Then I will see the uaclient version on stdout - When I run `pro version` with sudo - Then I will see the uaclient version on stdout - When I run `pro --version` as non-root - Then I will see the uaclient version on stdout - When I run `pro --version` with sudo - Then I will see the uaclient version on stdout - Examples: ubuntu release - | release | machine_type | - | bionic | lxd-container | - | focal | lxd-container | - | xenial | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | + When I verify that running `pro auto-attach` `with sudo` exits `2` + Then stderr matches regexp: + """ + This machine is already attached to '.+' + To use a different subscription first run: sudo pro detach. + """ + # status command + When I run `pro status --format json` as non-root + Then stdout is a json matching the `ua_status` schema + When I run `pro status --format yaml` as non-root + Then stdout is a yaml matching the `ua_status` schema + When I create the file `/var/lib/ubuntu-advantage/machine-token-overlay.json` with the following: + """ + { + "machineTokenInfo": { + "contractInfo": { + "effectiveTo": null + } + } + } + """ + And I append the following on uaclient config: + """ + features: + machine_token_overlay: "/var/lib/ubuntu-advantage/machine-token-overlay.json" + """ + And I run `pro status` with sudo + Then stdout contains substring: + """ + Valid until: Unknown/Expired + """ + # api command invalid endpoint + When I verify that running `pro api invalid.endpoint` `with sudo` exits `1` + Then stdout matches regexp: + """ + {\"_schema_version\": \"v1\", \"data\": {\"meta\": {\"environment_vars\": \[]}}, \"errors\": \[{\"code\": \"api\-invalid\-endpoint", \"meta\": {\"endpoint\": \"invalid.endpoint\"}, \"title\": \"'invalid\.endpoint' is not a valid endpoint\"}], \"result\": \"failure\", \"version\": \".*\", \"warnings\": \[]} + """ + When I verify that running `pro api u.pro.version.v1 --args extra=arg` `with sudo` exits `1` + Then stdout matches regexp: + """ + {\"_schema_version\": \"v1\", \"data\": {\"meta\": {\"environment_vars\": \[]}}, \"errors\": \[{\"code\": \"api\-no\-argument\-for\-endpoint\", \"meta\": {\"endpoint\": \"u.pro.version.v1\"}, \"title\": \"u\.pro\.version\.v1 accepts no arguments\"}], \"result\": \"failure\", \"version\": \".*\", \"warnings\": \[]} + """ + # api command valid endpoint + When I run `pro api u.pro.version.v1` with sudo + Then stdout matches regexp: + """ + {\"_schema_version\": \"v1\", \"data\": {\"attributes\": {\"installed_version\": \".*\"}, \"meta\": {\"environment_vars\": \[]}, \"type\": \"Version\"}, \"errors\": \[], \"result\": \"success\", \"version\": \".*\", \"warnings\": \[]} + """ + When I run `UA_LOG_FILE=/tmp/some_file OTHER_ENVVAR=not_there pro api u.pro.version.v1` with sudo + Then stdout matches regexp: + """ + {\"_schema_version\": \"v1\", \"data\": {\"attributes\": {\"installed_version\": \".*\"}, \"meta\": {\"environment_vars\": \[{\"name\": \"UA_LOG_FILE\", \"value\": \"\/tmp\/some_file\"}]}, \"type\": \"Version\"}, \"errors\": \[], \"result\": \"success\", \"version\": \".*\", \"warnings\": \[]} + """ + When I run `ua api u.pro.attach.auto.should_auto_attach.v1` with sudo + Then stdout matches regexp: + """ + {"_schema_version": "v1", "data": {"attributes": {"should_auto_attach": false}, "meta": {"environment_vars": \[\]}, "type": "ShouldAutoAttach"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} + """ + # version + When I run `pro version` as non-root + Then I will see the uaclient version on stdout + When I run `pro version` with sudo + Then I will see the uaclient version on stdout + When I run `pro --version` as non-root + Then I will see the uaclient version on stdout + When I run `pro --version` with sudo + Then I will see the uaclient version on stdout + + Examples: ubuntu release + | release | machine_type | + | bionic | lxd-container | + | focal | lxd-container | + | xenial | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/install_uninstall.feature ubuntu-advantage-tools-32~16.04/features/install_uninstall.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/install_uninstall.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/install_uninstall.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,133 +1,131 @@ Feature: Pro Install and Uninstall related tests - Scenario Outline: Do not fail on postinst when cloud-id returns error - Given a `` `` machine with ubuntu-advantage-tools installed - When I delete the file `/run/cloud-init/instance-data.json` - Then I verify that running `dpkg-reconfigure ubuntu-advantage-tools` `with sudo` exits `0` - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - @uses.config.contract_token - Scenario Outline: Purge package after attaching it to a machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `touch /etc/apt/preferences.d/ubuntu-esm-infra` with sudo - Then I verify that files exist matching `/var/log/ubuntu-advantage.log` - And I verify that running `test -d /var/lib/ubuntu-advantage` `with sudo` exits `0` - And I verify that files exist matching `/etc/apt/auth.conf.d/90ubuntu-advantage` - And I verify that files exist matching `/etc/apt/trusted.gpg.d/ubuntu-pro-esm-infra.gpg` - And I verify that files exist matching `/etc/apt/sources.list.d/ubuntu-esm-infra.list` - And I verify that files exist matching `/etc/apt/preferences.d/ubuntu-esm-infra` - When I run `apt purge ubuntu-pro-client -y` with sudo, retrying exit [100] - Then stdout matches regexp: - """ - Purging configuration files for ubuntu-advantage-tools - """ - And I verify that no files exist matching `/var/log/ubuntu-advantage.log` - And I verify that no files exist matching `/var/lib/ubuntu-advantage` - And I verify that no files exist matching `/etc/apt/auth.conf.d/90ubuntu-advantage` - And I verify that no files exist matching `/etc/apt/sources.list.d/ubuntu-*` - And I verify that no files exist matching `/etc/apt/trusted.gpg.d/ubuntu-pro-*` - And I verify that no files exist matching `/etc/apt/preferences.d/ubuntu-*` - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - @slow - Scenario Outline: Do not fail during postinst with nonstandard python setup - Given a `` `` machine with ubuntu-advantage-tools installed - # Works when in a python virtualenv - When I apt update - And I apt install `python3-venv` - And I run `python3 -m venv env` with sudo - Then I verify that running `bash -c ". env/bin/activate && python3 -c 'import uaclient'"` `with sudo` exits `1` - Then stderr matches regexp: - """ - No module named 'uaclient' - """ - Then I verify that running `bash -c ". env/bin/activate && dpkg-reconfigure ubuntu-advantage-tools"` `with sudo` exits `0` - - # Works with python built/installed from source - When I run `wget https://www.python.org/ftp/python/3.10.0/Python-3.10.0.tgz` with sudo - When I run `tar -xvf Python-3.10.0.tgz` with sudo - When I apt install `build-essential zlib1g-dev` - When I run `sh -c "cd Python-3.10.0 && ./configure"` with sudo - When I run `make -C Python-3.10.0` with sudo - When I run `make -C Python-3.10.0 install` with sudo - When I run `python3 --version` with sudo - Then I will see the following on stdout - """ - Python 3.10.0 - """ - Then I verify that running `python3 -c "import uaclient"` `with sudo` exits `1` - Then stderr matches regexp: - """ - No module named 'uaclient' - """ - Then I verify that running `dpkg-reconfigure ubuntu-advantage-tools` `with sudo` exits `0` - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - @skip_local_environment - @skip_prebuilt_environment - Scenario Outline: Package ubuntu-advantage-tools now install - Given a `` `` machine - When I install transition package ubuntu-advantage-tools - Then I verify that `ubuntu-pro-client` is installed - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - - @skip_local_environment - @skip_prebuilt_environment - Scenario Outline: Package ubuntu-advantage-tools now install - Given a `` `` machine - When I install transition package ubuntu-advantage-tools - Then I verify that `ubuntu-pro-auto-attach` is installed - - Examples: ubuntu release - | release | machine_type | - | xenial | aws.pro | - | bionic | aws.pro | - | focal | aws.pro | - | jammy | aws.pro | - | jammy | aws.pro | - - @skip_local_environment - @skip_prebuilt_environment - Scenario Outline: Does not cause deadlock when cloud-init installs ubuntu-advantage-tools - Given a `` `` machine with ubuntu-advantage-tools installed adding this cloud-init user_data - """ - : {} - """ - When I apt remove `ubuntu-advantage-tools ubuntu-pro-client` - When I run `cloud-init clean --logs` with sudo - When I reboot the machine - When I run `cloud-init status --wait` with sudo - Then I verify that `ubuntu-advantage-tools` is installed - - Examples: ubuntu release - | release | machine_type | user_data_field | - | xenial | lxd-container | ubuntu-advantage | - | bionic | lxd-container | ubuntu_advantage | - | focal | lxd-container | ubuntu_advantage | - | jammy | lxd-container | ubuntu_advantage | - | mantic | lxd-container | ubuntu_advantage | + Scenario Outline: Do not fail on postinst when cloud-id returns error + Given a `` `` machine with ubuntu-advantage-tools installed + When I delete the file `/run/cloud-init/instance-data.json` + Then I verify that running `dpkg-reconfigure ubuntu-advantage-tools` `with sudo` exits `0` + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + @uses.config.contract_token + Scenario Outline: Purge package after attaching it to a machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `touch /etc/apt/preferences.d/ubuntu-esm-infra` with sudo + Then I verify that files exist matching `/var/log/ubuntu-advantage.log` + And I verify that running `test -d /var/lib/ubuntu-advantage` `with sudo` exits `0` + And I verify that files exist matching `/etc/apt/auth.conf.d/90ubuntu-advantage` + And I verify that files exist matching `/etc/apt/trusted.gpg.d/ubuntu-pro-esm-infra.gpg` + And I verify that files exist matching `/etc/apt/sources.list.d/ubuntu-esm-infra.list` + And I verify that files exist matching `/etc/apt/preferences.d/ubuntu-esm-infra` + When I run `apt purge ubuntu-advantage-tools ubuntu-pro-client -y` with sudo, retrying exit [100] + Then stdout matches regexp: + """ + Purging configuration files for ubuntu-advantage-tools + """ + And I verify that no files exist matching `/var/log/ubuntu-advantage.log` + And I verify that no files exist matching `/var/lib/ubuntu-advantage` + And I verify that no files exist matching `/etc/apt/auth.conf.d/90ubuntu-advantage` + And I verify that no files exist matching `/etc/apt/sources.list.d/ubuntu-*` + And I verify that no files exist matching `/etc/apt/trusted.gpg.d/ubuntu-pro-*` + And I verify that no files exist matching `/etc/apt/preferences.d/ubuntu-*` + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + + @slow + Scenario Outline: Do not fail during postinst with nonstandard python setup + Given a `` `` machine with ubuntu-advantage-tools installed + # Works when in a python virtualenv + When I apt install `python3-venv` + And I run `python3 -m venv env` with sudo + Then I verify that running `bash -c ". env/bin/activate && python3 -c 'import uaclient'"` `with sudo` exits `1` + Then stderr matches regexp: + """ + No module named 'uaclient' + """ + Then I verify that running `bash -c ". env/bin/activate && dpkg-reconfigure ubuntu-advantage-tools"` `with sudo` exits `0` + # Works with python built/installed from source + When I run `wget https://www.python.org/ftp/python/3.10.0/Python-3.10.0.tgz` with sudo + When I run `tar -xvf Python-3.10.0.tgz` with sudo + When I apt install `build-essential zlib1g-dev` + When I run `sh -c "cd Python-3.10.0 && ./configure"` with sudo + When I run `make -C Python-3.10.0` with sudo + When I run `make -C Python-3.10.0 install` with sudo + When I run `python3 --version` with sudo + Then I will see the following on stdout + """ + Python 3.10.0 + """ + Then I verify that running `python3 -c "import uaclient"` `with sudo` exits `1` + Then stderr matches regexp: + """ + No module named 'uaclient' + """ + Then I verify that running `dpkg-reconfigure ubuntu-advantage-tools` `with sudo` exits `0` + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + @skip_local_environment @skip_prebuilt_environment + Scenario Outline: Package ubuntu-advantage-tools now install + Given a `` `` machine + When I install transition package ubuntu-advantage-tools + Then I verify that `ubuntu-pro-client` is installed + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + + @skip_local_environment @skip_prebuilt_environment + Scenario Outline: Package ubuntu-advantage-tools now install + Given a `` `` machine + When I install transition package ubuntu-advantage-tools + Then I verify that `ubuntu-pro-auto-attach` is installed + + Examples: ubuntu release + | release | machine_type | + | xenial | aws.pro | + | bionic | aws.pro | + | focal | aws.pro | + | jammy | aws.pro | + | jammy | aws.pro | + + @skip_local_environment @skip_prebuilt_environment + Scenario Outline: Does not cause deadlock when cloud-init installs ubuntu-advantage-tools + Given a `` `` machine with ubuntu-advantage-tools installed adding this cloud-init user_data + """ + : {} + """ + When I apt remove `ubuntu-advantage-tools ubuntu-pro-client` + When I run `cloud-init clean --logs` with sudo + When I reboot the machine + When I run `cloud-init status --wait` with sudo + Then I verify that `ubuntu-advantage-tools` is installed + + Examples: ubuntu release + | release | machine_type | user_data_field | + | xenial | lxd-container | ubuntu-advantage | + | bionic | lxd-container | ubuntu_advantage | + | focal | lxd-container | ubuntu_advantage | + | jammy | lxd-container | ubuntu_advantage | + | mantic | lxd-container | ubuntu_advantage | + | noble | lxd-container | ubuntu_advantage | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/landscape.feature ubuntu-advantage-tools-32~16.04/features/landscape.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/landscape.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/landscape.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,355 +1,334 @@ -@uses.config.contract_token -@uses.config.landscape_registration_key -@uses.config.landscape_api_access_key -@uses.config.landscape_api_secret_key +@uses.config.contract_token @uses.config.landscape_registration_key @uses.config.landscape_api_access_key @uses.config.landscape_api_secret_key Feature: Enable landscape on Ubuntu - Scenario Outline: Enable Landscape non-interactively - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo and options `--no-auto-enable` - - Then I verify that running `pro enable landscape` `as non-root` exits `1` - And I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - - When I run `pro enable landscape -- --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key $behave_var{config landscape_registration_key} --silent` with sudo - Then stdout contains substring: - """ - One moment, checking your subscription first - Updating standard Ubuntu package lists - Installing landscape-client - Executing `landscape-config --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key --silent` - """ - Then stdout contains substring - """ - Registration request sent successfully. - """ - And I verify that `landscape` is enabled - - When I run `sudo pro disable landscape` with sudo - Then I verify that `landscape` is disabled - - # Enable with assume-yes - When I run `pro enable landscape --assume-yes -- --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key $behave_var{config landscape_registration_key}` with sudo - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Updating standard Ubuntu package lists - Installing landscape-client - Executing `landscape-config --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key --silent` - Landscape enabled - """ - And I verify that `landscape` is enabled - - # stopping the service effectively disables it - When I run `systemctl stop landscape-client` with sudo - Then I verify that `landscape` is disabled - When I verify that running `sudo pro disable landscape` `with sudo` exits `1` - Then I will see the following on stdout: - """ - Landscape is not currently enabled - See: sudo pro status - """ - - # Fail to enable with assume-yes - When I verify that running `pro enable landscape --assume-yes -- --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key wrong` `with sudo` exits `1` - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Updating standard Ubuntu package lists - Installing landscape-client - Executing `landscape-config --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key --silent` - Invalid account name or registration key. - Could not enable Landscape. - """ - # This will become obsolete soon: #2864 - When I run `pro status` with sudo - # I am keeping this check until the non-root landscape-config check works as expected - Then stdout matches regexp: - """ - landscape +yes +warning - """ - Then stdout contains substring: - """ - Landscape is installed and configured but not registered. - Run `sudo landscape-config` to register, or run `sudo pro disable landscape` - """ - When I run `sudo pro disable landscape` with sudo - When I run `pro status` with sudo - Then stdout matches regexp: - """ - landscape +yes +disabled - """ - - # Enable with assume-yes and format json - When I run `pro enable landscape --assume-yes --format=json -- --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key $behave_var{config landscape_registration_key}` with sudo - Then I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["landscape"], "result": "success", "warnings": []} - """ - And I verify that `landscape` is enabled - When I run `sudo pro disable landscape` with sudo - - # Fail to enable with assume-yes and format json - When I verify that running `pro enable landscape --assume-yes --format=json -- --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key wrong` `with sudo` exits `1` - Then I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"additional_info": {"stderr": "Invalid account name or registration key.", "stdout": ""}, "message": "landscape-config command failed", "message_code": "landscape-config-failed", "service": "landscape", "type": "service"}], "failed_services": ["landscape"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - # This will become obsolete soon: #2864 - When I run `pro status` with sudo - # I am keeping this check until the non-root landscape-config check works as expected - Then stdout matches regexp: - """ - landscape +yes +warning - """ - Then stdout contains substring: - """ - Landscape is installed and configured but not registered. - Run `sudo landscape-config` to register, or run `sudo pro disable landscape` - """ - Examples: ubuntu release - | release | machine_type | - | mantic | lxd-container | - - Scenario Outline: Enable Landscape interactively - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo and options `--no-auto-enable` - - Then I verify that running `pro enable landscape` `as non-root` exits `1` - And I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - - When I run `pro enable landscape` `with sudo` and the following stdin - # This will change in the future, but right now the lines are: - # use self-hosted? - # computer title - # account name - # registration key - # confirm registration key - # http proxy - # https proxy - # request registration - """ - n - $behave_var{machine-name system-under-test} - pro-client-qa - $behave_var{config landscape_registration_key} - $behave_var{config landscape_registration_key} - - - y - """ - Then stdout contains substring: - """ - One moment, checking your subscription first - Updating standard Ubuntu package lists - Installing landscape-client - Executing `landscape-config` - """ - Then stdout contains substring: - """ - Registration request sent successfully. - """ - And I verify that `landscape` is enabled - When I run `pro disable landscape` with sudo - - When I verify that running `pro enable landscape` `with sudo` and the following stdin exits `1` - """ - n - $behave_var{machine-name system-under-test} - pro-client-qa - wrong - wrong - - - y - """ - Then stdout contains substring: - """ - One moment, checking your subscription first - Updating standard Ubuntu package lists - Installing landscape-client - Executing `landscape-config` - """ - And stderr contains substring: - """ - Invalid account name or registration key. - """ - When I run `pro status` with sudo - Then stdout contains substring: - """ - Landscape is installed and configured but not registered. - Run `sudo landscape-config` to register, or run `sudo pro disable landscape` - """ - Examples: ubuntu release - | release | machine_type | - | mantic | lxd-container | - - Scenario Outline: Easily re-enable Landscape non-interactively after a disable - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo and options `--no-auto-enable` - - When I run `pro enable landscape --assume-yes -- --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key $behave_var{config landscape_registration_key}` with sudo - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Updating standard Ubuntu package lists - Installing landscape-client - Executing `landscape-config --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key --silent` - Landscape enabled - """ - When I run `pro status` with sudo - Then stdout matches regexp: - """ - landscape +yes +enabled - """ - - When I run `pro disable landscape` with sudo - Then I will see the following on stdout: - """ - Executing `landscape-config --disable` - /etc/landscape/client.conf contains your landscape-client configuration. - To re-enable Landscape with the same configuration, run: - sudo pro enable landscape --assume-yes - """ - When I run `pro status` with sudo - Then stdout matches regexp: - """ - landscape +yes +disabled - """ - - When I run `pro enable landscape --assume-yes` with sudo - When I run `pro status` with sudo - Then stdout matches regexp: - """ - landscape +yes +enabled - """ - When I run shell command `cat /etc/landscape/client.conf | grep computer_title` with sudo - Then I will see the following on stdout: - """ - computer_title = $behave_var{machine-name system-under-test} - """ - When I run shell command `cat /etc/landscape/client.conf | grep account_name` with sudo - Then I will see the following on stdout: - """ - account_name = pro-client-qa - """ - - # Now do the same test but with a full detach - When I run `pro detach --assume-yes` with sudo - Then I will see the following on stdout: - """ - Detach will disable the following service: - landscape - Executing `landscape-config --disable` - /etc/landscape/client.conf contains your landscape-client configuration. - To re-enable Landscape with the same configuration, run: - sudo pro enable landscape --assume-yes - - This machine is now detached. - """ - When I run `pro api u.pro.status.is_attached.v1` with sudo - Then stdout contains substring: - """ - "is_attached": false - """ - - When I attach `contract_token` with sudo and options `--no-auto-enable` - When I run `pro enable landscape --assume-yes` with sudo - When I run `pro status` with sudo - Then stdout matches regexp: - """ - landscape +yes +enabled - """ - When I run shell command `cat /etc/landscape/client.conf | grep computer_title` with sudo - Then I will see the following on stdout: - """ - computer_title = $behave_var{machine-name system-under-test} - """ - When I run shell command `cat /etc/landscape/client.conf | grep account_name` with sudo - Then I will see the following on stdout: - """ - account_name = pro-client-qa - """ - Examples: ubuntu release - | release | machine_type | - | mantic | lxd-container | - - Scenario Outline: Detaching/reattaching on an unsupported release does not affect landscape - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo and options `--no-auto-enable` - When I run `pro status` with sudo - Then stdout does not contain substring: - """ - landscape - """ - - When I apt install `landscape-client` - - # assert pre-enabled state - When I verify that running `systemctl is-active landscape-client` `with sudo` exits `3` - Then I will see the following on stdout: - """ - inactive - """ - - # enable with landscape-config directly - When I run `landscape-config --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key $behave_var{config landscape_registration_key} --silent` with sudo - Then I will see the following on stdout: - """ - Please wait... - System successfully registered. - """ - - # assert that landscape is running, but pro doesn't care - When I verify that running `systemctl is-active landscape-client` `with sudo` exits `0` - Then I will see the following on stdout: - """ - active - """ - When I run `pro status` with sudo - Then stdout does not contain substring: - """ - landscape - """ - - # disable refuses - When I verify that running `pro disable landscape` `with sudo` exits `1` - Then I will see the following on stdout: - """ - Disabling Landscape with pro is not supported. - See: sudo pro status - """ - - # detach doesn't touch it - When I run `pro detach --assume-yes` with sudo - Then I will see the following on stdout: - """ - This machine is now detached. - """ - - # still running - When I verify that running `systemctl is-active landscape-client` `with sudo` exits `0` - Then I will see the following on stdout: - """ - active - """ - - # re-attaching doesn't affect it either - When I attach `contract_token` with sudo and options `--no-auto-enable` - - # still running - When I verify that running `systemctl is-active landscape-client` `with sudo` exits `0` - Then I will see the following on stdout: - """ - active - """ - - Examples: ubuntu release - | release | machine_type | - | jammy | lxd-container | + Scenario Outline: Enable Landscape non-interactively + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo and options `--no-auto-enable` + Then I verify that running `pro enable landscape` `as non-root` exits `1` + And I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + When I run `pro enable landscape -- --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key $behave_var{config landscape_registration_key} --silent` with sudo + Then stdout contains substring: + """ + One moment, checking your subscription first + Updating standard Ubuntu package lists + Installing landscape-client + Executing `landscape-config --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key --silent` + """ + Then stdout contains substring + """ + Registration request sent successfully. + """ + And I verify that `landscape` is enabled + When I run `sudo pro disable landscape` with sudo + Then I verify that `landscape` is disabled + # Enable with assume-yes + When I run `pro enable landscape --assume-yes -- --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key $behave_var{config landscape_registration_key}` with sudo + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Updating standard Ubuntu package lists + Installing landscape-client + Executing `landscape-config --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key --silent` + Landscape enabled + """ + And I verify that `landscape` is enabled + # stopping the service effectively disables it + When I run `systemctl stop landscape-client` with sudo + Then I verify that `landscape` is disabled + When I verify that running `sudo pro disable landscape` `with sudo` exits `1` + Then I will see the following on stdout: + """ + Landscape is not currently enabled - nothing to do. + See: sudo pro status + """ + # Fail to enable with assume-yes + When I verify that running `pro enable landscape --assume-yes -- --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key wrong` `with sudo` exits `1` + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Updating standard Ubuntu package lists + Installing landscape-client + Executing `landscape-config --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key --silent` + Invalid account name or registration key. + landscape-config command failed + Could not enable Landscape. + """ + # This will become obsolete soon: #2864 + When I run `pro status` with sudo + # I am keeping this check until the non-root landscape-config check works as expected + Then stdout matches regexp: + """ + landscape +yes +warning + """ + Then stdout contains substring: + """ + Landscape is installed and configured but not registered. + Run `sudo landscape-config` to register, or run `sudo pro disable landscape` + """ + When I run `sudo pro disable landscape` with sudo + When I run `pro status` with sudo + Then stdout matches regexp: + """ + landscape +yes +disabled + """ + # Enable with assume-yes and format json + When I run `pro enable landscape --assume-yes --format=json -- --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key $behave_var{config landscape_registration_key}` with sudo + Then I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["landscape"], "result": "success", "warnings": []} + """ + And I verify that `landscape` is enabled + When I run `sudo pro disable landscape` with sudo + # Fail to enable with assume-yes and format json + When I verify that running `pro enable landscape --assume-yes --format=json -- --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key wrong` `with sudo` exits `1` + Then I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"additional_info": {"stderr": "Invalid account name or registration key.", "stdout": ""}, "message": "landscape-config command failed", "message_code": "landscape-config-failed", "service": "landscape", "type": "service"}], "failed_services": ["landscape"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + # This will become obsolete soon: #2864 + When I run `pro status` with sudo + # I am keeping this check until the non-root landscape-config check works as expected + Then stdout matches regexp: + """ + landscape +yes +warning + """ + Then stdout contains substring: + """ + Landscape is installed and configured but not registered. + Run `sudo landscape-config` to register, or run `sudo pro disable landscape` + """ + + Examples: ubuntu release + | release | machine_type | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: Enable Landscape interactively + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo and options `--no-auto-enable` + Then I verify that running `pro enable landscape` `as non-root` exits `1` + And I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + When I run `pro enable landscape` `with sudo` and the following stdin + # This will change in the future, but right now the lines are: + # use self-hosted? + # computer title + # account name + # registration key + # confirm registration key + # http proxy + # https proxy + # request registration + """ + n + $behave_var{machine-name system-under-test} + pro-client-qa + $behave_var{config landscape_registration_key} + $behave_var{config landscape_registration_key} + + + y + """ + Then stdout contains substring: + """ + One moment, checking your subscription first + Updating standard Ubuntu package lists + Installing landscape-client + Executing `landscape-config` + """ + Then stdout contains substring: + """ + Registration request sent successfully. + """ + And I verify that `landscape` is enabled + When I run `pro disable landscape` with sudo + When I verify that running `pro enable landscape` `with sudo` and the following stdin exits `1` + """ + n + $behave_var{machine-name system-under-test} + pro-client-qa + wrong + wrong + + + y + """ + Then stdout contains substring: + """ + One moment, checking your subscription first + Updating standard Ubuntu package lists + Installing landscape-client + Executing `landscape-config` + """ + And stderr contains substring: + """ + Invalid account name or registration key. + """ + When I run `pro status` with sudo + Then stdout contains substring: + """ + Landscape is installed and configured but not registered. + Run `sudo landscape-config` to register, or run `sudo pro disable landscape` + """ + + Examples: ubuntu release + | release | machine_type | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: Easily re-enable Landscape non-interactively after a disable + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo and options `--no-auto-enable` + When I run `pro enable landscape --assume-yes -- --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key $behave_var{config landscape_registration_key}` with sudo + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Updating standard Ubuntu package lists + Installing landscape-client + Executing `landscape-config --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key --silent` + Landscape enabled + """ + When I run `pro status` with sudo + Then stdout matches regexp: + """ + landscape +yes +enabled + """ + When I run `pro disable landscape` with sudo + Then I will see the following on stdout: + """ + Executing `landscape-config --disable` + /etc/landscape/client.conf contains your landscape-client configuration. + To re-enable Landscape with the same configuration, run: + sudo pro enable landscape --assume-yes + """ + When I run `pro status` with sudo + Then stdout matches regexp: + """ + landscape +yes +disabled + """ + When I run `pro enable landscape --assume-yes` with sudo + When I run `pro status` with sudo + Then stdout matches regexp: + """ + landscape +yes +enabled + """ + When I run shell command `cat /etc/landscape/client.conf | grep computer_title` with sudo + Then I will see the following on stdout: + """ + computer_title = $behave_var{machine-name system-under-test} + """ + When I run shell command `cat /etc/landscape/client.conf | grep account_name` with sudo + Then I will see the following on stdout: + """ + account_name = pro-client-qa + """ + # Now do the same test but with a full detach + When I run `pro detach --assume-yes` with sudo + Then I will see the following on stdout: + """ + Detach will disable the following service: + landscape + Executing `landscape-config --disable` + /etc/landscape/client.conf contains your landscape-client configuration. + To re-enable Landscape with the same configuration, run: + sudo pro enable landscape --assume-yes + + This machine is now detached. + """ + When I run `pro api u.pro.status.is_attached.v1` with sudo + Then stdout contains substring: + """ + "is_attached": false + """ + When I attach `contract_token` with sudo and options `--no-auto-enable` + When I run `pro enable landscape --assume-yes` with sudo + When I run `pro status` with sudo + Then stdout matches regexp: + """ + landscape +yes +enabled + """ + When I run shell command `cat /etc/landscape/client.conf | grep computer_title` with sudo + Then I will see the following on stdout: + """ + computer_title = $behave_var{machine-name system-under-test} + """ + When I run shell command `cat /etc/landscape/client.conf | grep account_name` with sudo + Then I will see the following on stdout: + """ + account_name = pro-client-qa + """ + + Examples: ubuntu release + | release | machine_type | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: Detaching/reattaching on an unsupported release does not affect landscape + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo and options `--no-auto-enable` + When I run `pro status` with sudo + Then stdout does not contain substring: + """ + landscape + """ + When I apt install `landscape-client` + # assert pre-enabled state + When I verify that running `systemctl is-active landscape-client` `with sudo` exits `3` + Then I will see the following on stdout: + """ + inactive + """ + # enable with landscape-config directly + When I run `landscape-config --computer-title $behave_var{machine-name system-under-test} --account-name pro-client-qa --registration-key $behave_var{config landscape_registration_key} --silent` with sudo + Then I will see the following on stdout: + """ + Please wait... + System successfully registered. + """ + # assert that landscape is running, but pro doesn't care + When I verify that running `systemctl is-active landscape-client` `with sudo` exits `0` + Then I will see the following on stdout: + """ + active + """ + When I run `pro status` with sudo + Then stdout does not contain substring: + """ + landscape + """ + # disable refuses + When I verify that running `pro disable landscape` `with sudo` exits `1` + Then I will see the following on stdout: + """ + Disabling Landscape with pro is not supported. + See: sudo pro status + """ + # detach doesn't touch it + When I run `pro detach --assume-yes` with sudo + Then I will see the following on stdout: + """ + This machine is now detached. + """ + # still running + When I verify that running `systemctl is-active landscape-client` `with sudo` exits `0` + Then I will see the following on stdout: + """ + active + """ + # re-attaching doesn't affect it either + When I attach `contract_token` with sudo and options `--no-auto-enable` + # still running + When I verify that running `systemctl is-active landscape-client` `with sudo` exits `0` + Then I will see the following on stdout: + """ + active + """ + + Examples: ubuntu release + | release | machine_type | + | jammy | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/livepatch.feature ubuntu-advantage-tools-32~16.04/features/livepatch.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/livepatch.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/livepatch.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,208 +1,217 @@ @uses.config.contract_token Feature: Livepatch - Scenario Outline: Unattached livepatch status shows warning when on unsupported kernel - Given a `` `` machine with ubuntu-advantage-tools installed - When I change config key `livepatch_url` to use value `` - Then I verify that no files exist matching `/home/ubuntu/.cache/ubuntu-pro/livepatch-kernel-support-cache.json` - # This is needed because `apt update` creates this file before, and we need to make sure it is created correctly later - When I delete the file `/run/ubuntu-advantage/livepatch-kernel-support-cache.json` - When I run `pro status` as non-root - Then I verify that files exist matching `/home/ubuntu/.cache/ubuntu-pro/livepatch-kernel-support-cache.json` - Then I verify that no files exist matching `/run/ubuntu-advantage/livepatch-kernel-support-cache.json` - When I run `pro status` with sudo - Then stdout matches regexp: - """ - livepatch +yes +Current kernel is not supported - """ - Then stdout contains substring: - """ - Supported livepatch kernels are listed here: https://ubuntu.com/security/livepatch/docs/kernels - """ - Then I verify that files exist matching `/run/ubuntu-advantage/livepatch-kernel-support-cache.json` - When I apt install `linux-generic` - When I apt remove `linux-image*-kvm` - When I run `update-grub` with sudo - When I reboot the machine - When I run `pro status` with sudo - Then stdout matches regexp: - """ - livepatch +yes +Canonical Livepatch service - """ - Then stdout does not contain substring: - """ - Supported livepatch kernels are listed here: https://ubuntu.com/security/livepatch/docs/kernels - """ - Examples: ubuntu release - | release | machine_type | livepatch_url | - | focal | lxd-vm | https://livepatch.canonical.com | - | focal | lxd-vm | https://livepatch.staging.canonical.com | - - Scenario Outline: Attached livepatch status shows warning when on unsupported kernel - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - When I run `pro status` with sudo - Then stdout matches regexp: - """ - livepatch +yes +warning +Current kernel is not supported - """ - Then stdout matches regexp: - """ - NOTICES - The current kernel \(5.4.0-(\d+)-kvm, x86_64\) is not supported by livepatch. - Supported kernels are listed here: https://ubuntu.com/security/livepatch/docs/kernels - Either switch to a supported kernel or `pro disable livepatch` to dismiss this warning. - - """ - When I run `pro disable livepatch` with sudo - When I run `pro status` with sudo - Then stdout matches regexp: - """ - livepatch +yes +disabled +Current kernel is not supported - """ - Then stdout does not match regexp: - """ - NOTICES - The current kernel \(5.4.0-(\d+)-kvm, x86_64\) is not supported by livepatch. - Supported kernels are listed here: https://ubuntu.com/security/livepatch/docs/kernels - Either switch to a supported kernel or `pro disable livepatch` to dismiss this warning. - - """ - When I apt install `linux-generic` - When I apt remove `linux-image*-kvm` - When I run `update-grub` with sudo - When I reboot the machine - When I run `pro status` with sudo - Then stdout matches regexp: - """ - livepatch +yes +disabled +Canonical Livepatch service - """ - When I run `pro enable livepatch` with sudo - When I run `pro status` with sudo - Then stdout matches regexp: - """ - livepatch +yes +enabled +Canonical Livepatch service - """ - Examples: ubuntu release - | release | machine_type | - | focal | lxd-vm | - - Scenario Outline: Attached livepatch status shows upgrade required when on an old kernel - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token_staging` with sudo - When I apt install `linux-headers- linux-image-` - When I apt remove `linux-image*-gcp` - When I run `update-grub` with sudo - When I reboot the machine - When I run `uname -r` with sudo - Then stdout contains substring: - """ - - """ - And I verify that `livepatch` status is warning - When I run `pro status` with sudo - Then stdout contains substring: - """ - NOTICES - The running kernel has reached the end of its active livepatch window. - Please upgrade the kernel with apt and reboot for continued livepatch support. - - """ - When I apt install `linux-headers-generic linux-image-generic` - When I reboot the machine - When I run `uname -r` with sudo - Then stdout does not contain substring: - """ - - """ - And I verify that `livepatch` is enabled - Then stdout does not contain substring: - """ - NOTICES - The running kernel has reached the end of its active livepatch window. - Please upgrade the kernel with apt and reboot for continued livepatch support. - - """ - Examples: ubuntu release - | release | machine_type | old_kernel_version | - | focal | gcp.generic | 5.4.0-28-generic | - - Scenario Outline: Livepatch is not enabled by default and can't be enabled on interim releases - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro status --all` with sudo - Then stdout matches regexp: - """ - livepatch +no +Current kernel is not supported - """ - When I attach `contract_token` with sudo - When I run `pro status --all` with sudo - Then stdout matches regexp: - """ - livepatch +yes +n/a +Canonical Livepatch service - """ - When I verify that running `pro enable livepatch` `with sudo` exits `1` - Then stdout contains substring: - """ - Livepatch is not available for Ubuntu . - """ - When I run `pro status --all` with sudo - Then stdout matches regexp: - """ - livepatch +yes +n/a +Canonical Livepatch service - """ - Examples: ubuntu release - | release | machine_type | pretty_name | - | mantic | lxd-vm | 23.10 (Mantic Minotaur) | - - Scenario Outline: Livepatch is supported on interim HWE kernel - # This test is intended to ensure that an interim HWE kernel has the correct support status - # It should be kept up to date so that it runs on the latest LTS and installs the latest - # HWE kernel for that release. - Given a `` `` machine with ubuntu-advantage-tools installed - When I apt update - When I apt install `linux-generic-hwe-` - When I apt remove `linux-image*-kvm` - When I run `update-grub` with sudo - When I reboot the machine - When I attach `contract_token` with sudo - When I run `pro status` with sudo - Then I verify that `livepatch` is enabled - - Examples: ubuntu release - | release | machine_type | release_num | - | jammy | lxd-vm | 22.04 | - - Scenario Outline: snapd installed as a snap if necessary - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `snap list` with sudo - Then stdout does not contain substring: - """ - snapd - """ - When I set the machine token overlay to the following yaml - """ - machineTokenInfo: - contractInfo: - resourceEntitlements: - - type: livepatch - directives: - requiredSnaps: - - name: core22 - """ - When I attach `contract_token` with sudo - Then stdout contains substring: - """ - Installing snapd snap - """ - When I run `snap list` with sudo - Then stdout contains substring: - """ - snapd - """ - And stdout contains substring: - """ - core22 - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-vm | + Scenario Outline: Livepatch is enabled by default + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + Then I verify that `livepatch` is enabled + + Examples: ubuntu release + | release | machine_type | + | noble | lxd-vm | + + Scenario Outline: Unattached livepatch status shows warning when on unsupported kernel + Given a `` `` machine with ubuntu-advantage-tools installed + When I change config key `livepatch_url` to use value `` + Then I verify that no files exist matching `/home/ubuntu/.cache/ubuntu-pro/livepatch-kernel-support-cache.json` + # This is needed because `apt update` creates this file before, and we need to make sure it is created correctly later + When I delete the file `/run/ubuntu-advantage/livepatch-kernel-support-cache.json` + When I run `pro status` as non-root + Then I verify that files exist matching `/home/ubuntu/.cache/ubuntu-pro/livepatch-kernel-support-cache.json` + Then I verify that no files exist matching `/run/ubuntu-advantage/livepatch-kernel-support-cache.json` + When I run `pro status` with sudo + Then stdout matches regexp: + """ + livepatch +yes +Current kernel is not supported + """ + Then stdout contains substring: + """ + Supported livepatch kernels are listed here: https://ubuntu.com/security/livepatch/docs/kernels + """ + Then I verify that files exist matching `/run/ubuntu-advantage/livepatch-kernel-support-cache.json` + When I apt install `linux-generic` + When I apt remove `linux-image*-kvm` + When I run `update-grub` with sudo + When I reboot the machine + When I run `pro status` with sudo + Then stdout matches regexp: + """ + livepatch +yes +Canonical Livepatch service + """ + Then stdout does not contain substring: + """ + Supported livepatch kernels are listed here: https://ubuntu.com/security/livepatch/docs/kernels + """ + + Examples: ubuntu release + | release | machine_type | livepatch_url | + | focal | lxd-vm | https://livepatch.canonical.com | + | focal | lxd-vm | https://livepatch.staging.canonical.com | + + Scenario Outline: Attached livepatch status shows warning when on unsupported kernel + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + When I run `pro status` with sudo + Then stdout matches regexp: + """ + livepatch +yes +warning +Current kernel is not supported + """ + Then stdout matches regexp: + """ + NOTICES + The current kernel \(5.4.0-(\d+)-kvm, x86_64\) is not supported by livepatch. + Supported kernels are listed here: https://ubuntu.com/security/livepatch/docs/kernels + Either switch to a supported kernel or `sudo pro disable livepatch` to dismiss this warning. + """ + When I run `pro disable livepatch` with sudo + When I run `pro status` with sudo + Then stdout matches regexp: + """ + livepatch +yes +disabled +Current kernel is not supported + """ + Then stdout does not match regexp: + """ + NOTICES + The current kernel \(5.4.0-(\d+)-kvm, x86_64\) is not supported by livepatch. + Supported kernels are listed here: https://ubuntu.com/security/livepatch/docs/kernels + Either switch to a supported kernel or `sudo pro disable livepatch` to dismiss this warning. + """ + When I apt install `linux-generic` + When I apt remove `linux-image*-kvm` + When I run `update-grub` with sudo + When I reboot the machine + When I run `pro status` with sudo + Then stdout matches regexp: + """ + livepatch +yes +disabled +Canonical Livepatch service + """ + When I run `pro enable livepatch` with sudo + When I run `pro status` with sudo + Then stdout matches regexp: + """ + livepatch +yes +enabled +Canonical Livepatch service + """ + + Examples: ubuntu release + | release | machine_type | + | focal | lxd-vm | + + Scenario Outline: Attached livepatch status shows upgrade required when on an old kernel + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token_staging` with sudo + When I apt install `linux-headers- linux-image-` + When I apt remove `linux-image*-gcp` + When I run `update-grub` with sudo + When I reboot the machine + When I run `uname -r` with sudo + Then stdout contains substring: + """ + + """ + And I verify that `livepatch` status is warning + When I run `pro status` with sudo + Then stdout contains substring: + """ + NOTICES + The running kernel has reached the end of its active livepatch window. + Please upgrade the kernel with apt and reboot for continued livepatch support. + """ + When I apt install `linux-headers-generic linux-image-generic` + When I reboot the machine + When I run `uname -r` with sudo + Then stdout does not contain substring: + """ + + """ + And I verify that `livepatch` is enabled + Then stdout does not contain substring: + """ + NOTICES + The running kernel has reached the end of its active livepatch window. + Please upgrade the kernel with apt and reboot for continued livepatch support. + """ + + Examples: ubuntu release + | release | machine_type | old_kernel_version | + | focal | gcp.generic | 5.4.0-28-generic | + + Scenario Outline: Livepatch is not enabled by default and can't be enabled on interim releases + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro status --all` with sudo + Then stdout matches regexp: + """ + livepatch +no +Current kernel is not supported + """ + When I attach `contract_token` with sudo + When I run `pro status --all` with sudo + Then stdout matches regexp: + """ + livepatch +yes +n/a +Canonical Livepatch service + """ + When I verify that running `pro enable livepatch` `with sudo` exits `1` + Then stdout contains substring: + """ + Livepatch is not available for Ubuntu . + """ + When I run `pro status --all` with sudo + Then stdout matches regexp: + """ + livepatch +yes +n/a +Canonical Livepatch service + """ + + Examples: ubuntu release + | release | machine_type | pretty_name | + | mantic | lxd-vm | 23.10 (Mantic Minotaur) | + + Scenario Outline: Livepatch is supported on interim HWE kernel + # This test is intended to ensure that an interim HWE kernel has the correct support status + # It should be kept up to date so that it runs on the latest LTS and installs the latest + # HWE kernel for that release. + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt install `linux-generic-hwe-` + When I apt remove `linux-image*-kvm` + When I run `update-grub` with sudo + When I reboot the machine + When I attach `contract_token` with sudo + When I run `pro status` with sudo + Then I verify that `livepatch` is enabled + + Examples: ubuntu release + | release | machine_type | release_num | + | jammy | lxd-vm | 22.04 | + + Scenario Outline: snapd installed as a snap if necessary + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `snap list` with sudo + Then stdout does not contain substring: + """ + snapd + """ + When I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + resourceEntitlements: + - type: livepatch + directives: + requiredSnaps: + - name: core22 + """ + When I attach `contract_token` with sudo + Then stdout contains substring: + """ + Installing snapd snap + """ + When I run `snap list` with sudo + Then stdout contains substring: + """ + snapd + """ + And stdout contains substring: + """ + core22 + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-vm | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/logs.feature ubuntu-advantage-tools-32~16.04/features/logs.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/logs.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/logs.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,130 +1,136 @@ Feature: Logs in Json Array Formatter - @uses.config.contract_token - Scenario Outline: The log file can be successfully parsed as json array - Given a `` `` machine with ubuntu-advantage-tools installed - When I apt update - And I apt install `jq` - And I verify that running `pro status` `with sudo` exits `0` - And I verify that running `pro enable test_entitlement` `with sudo` exits `1` - And I run shell command `tail /var/log/ubuntu-advantage.log | jq -r .` as non-root - Then I will see the following on stderr - """ - """ - When I attach `contract_token` with sudo - And I verify that running `pro refresh` `with sudo` exits `0` - And I verify that running `pro status` `with sudo` exits `0` - And I verify that running `pro enable test_entitlement` `with sudo` exits `1` - And I run shell command `tail /var/log/ubuntu-advantage.log | jq -r .` as non-root - Then I will see the following on stderr - """ - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - Scenario Outline: Non-root user and root user log files are different - Given a `` `` machine with ubuntu-advantage-tools installed - # Confirm user log file does not exist - When I run `truncate -s 0 /var/log/ubuntu-advantage.log` with sudo - And I verify `/var/log/ubuntu-advantage.log` is empty - Then I verify that no files exist matching `/home/ubuntu/.cache/ubuntu-pro/ubuntu-pro.log` - When I verify that running `pro status` `as non-root` exits `0` - Then I verify that files exist matching `/home/ubuntu/.cache/ubuntu-pro/ubuntu-pro.log` - When I verify `/var/log/ubuntu-advantage.log` is empty - And I run `cat /home/ubuntu/.cache/ubuntu-pro/ubuntu-pro.log` as non-root - Then stdout contains substring - """ - Executed with sys.argv: ['/usr/bin/pro', 'status'] - """ - When I run `truncate -s 0 /home/ubuntu/.cache/ubuntu-pro/ubuntu-pro.log` with sudo - And I attach `contract_token` with sudo - And I verify `/home/ubuntu/.cache/ubuntu-pro/ubuntu-pro.log` is empty - And I run `cat /var/log/ubuntu-advantage.log` as non-root - Then stdout contains substring - """ - Executed with sys.argv: ['/usr/bin/pro', 'attach' - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - Scenario Outline: Non-root user log files included in collect logs - Given a `` `` machine with ubuntu-advantage-tools installed - When i verify that running `pro status` `with sudo` exits `0` - And I verify that running `pro collect-logs` `with sudo` exits `0` - And I run `tar -tf ua_logs.tar.gz` as non-root - Then stdout does not contain substring - """ - user0.log - """ - When i verify that running `pro status` `as non-root` exits `0` - And I verify that running `pro collect-logs` `with sudo` exits `0` - And I run `tar -tf ua_logs.tar.gz` as non-root - Then stdout contains substring - """ - user0.log - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - Scenario Outline: logrotate configuration works - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro status` with sudo - And I run `sh -c "ls /var/log/ubuntu-advantage* | sort -d"` as non-root - Then stdout contains substring: - """ - /var/log/ubuntu-advantage.log - """ - Then stdout does not contain substring: - """ - /var/log/ubuntu-advantage.log.1 - """ - When I run `logrotate --force /etc/logrotate.d/ubuntu-pro-client` with sudo - And I run `sh -c "ls /var/log/ubuntu-advantage* | sort -d"` as non-root - Then stdout contains substring: - """ - /var/log/ubuntu-advantage.log - /var/log/ubuntu-advantage.log.1 - """ - # reset and run logrotate with full config - When I run `rm /var/log/ubuntu-advantage.log.1` with sudo - When I run `pro status` with sudo - And I run `sh -c "ls /var/log/ubuntu-advantage* | sort -d"` as non-root - Then stdout contains substring: - """ - /var/log/ubuntu-advantage.log - """ - Then stdout does not contain substring: - """ - /var/log/ubuntu-advantage.log.1 - """ - # This uses all logrotate config files on the system - When I run `logrotate --force /etc/logrotate.conf` with sudo - And I run `sh -c "ls /var/log/ubuntu-advantage* | sort -d"` as non-root - Then stdout contains substring: - """ - /var/log/ubuntu-advantage.log - /var/log/ubuntu-advantage.log.1 - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | + @uses.config.contract_token + Scenario Outline: The log file can be successfully parsed as json array + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt install `jq` + And I verify that running `pro status` `with sudo` exits `0` + And I verify that running `pro enable test_entitlement` `with sudo` exits `1` + And I run shell command `tail /var/log/ubuntu-advantage.log | jq -r .` as non-root + Then I will see the following on stderr + """ + """ + When I attach `contract_token` with sudo + And I verify that running `pro refresh` `with sudo` exits `0` + And I verify that running `pro status` `with sudo` exits `0` + And I verify that running `pro enable test_entitlement` `with sudo` exits `1` + And I run shell command `tail /var/log/ubuntu-advantage.log | jq -r .` as non-root + Then I will see the following on stderr + """ + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: Non-root user and root user log files are different + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `systemctl stop esm-cache.service` with sudo + # Confirm user log file does not exist + When I run `truncate -s 0 /var/log/ubuntu-advantage.log` with sudo + And I verify `/var/log/ubuntu-advantage.log` is empty + Then I verify that no files exist matching `/home/ubuntu/.cache/ubuntu-pro/ubuntu-pro.log` + When I verify that running `pro status` `as non-root` exits `0` + Then I verify that files exist matching `/home/ubuntu/.cache/ubuntu-pro/ubuntu-pro.log` + When I verify `/var/log/ubuntu-advantage.log` is empty + And I run `cat /home/ubuntu/.cache/ubuntu-pro/ubuntu-pro.log` as non-root + Then stdout contains substring + """ + Executed with sys.argv: ['/usr/bin/pro', 'status'] + """ + When I run `truncate -s 0 /home/ubuntu/.cache/ubuntu-pro/ubuntu-pro.log` with sudo + And I attach `contract_token` with sudo + And I verify `/home/ubuntu/.cache/ubuntu-pro/ubuntu-pro.log` is empty + And I run `cat /var/log/ubuntu-advantage.log` as non-root + Then stdout contains substring + """ + Executed with sys.argv: ['/usr/bin/pro', 'attach' + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: Non-root user log files included in collect logs + Given a `` `` machine with ubuntu-advantage-tools installed + When i verify that running `pro status` `with sudo` exits `0` + And I verify that running `pro collect-logs` `with sudo` exits `0` + And I run `tar -tf pro_logs.tar.gz` as non-root + Then stdout does not contain substring + """ + user0.log + """ + When i verify that running `pro status` `as non-root` exits `0` + And I verify that running `pro collect-logs` `with sudo` exits `0` + And I run `tar -tf pro_logs.tar.gz` as non-root + Then stdout contains substring + """ + user0.log + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: logrotate configuration works + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro status` with sudo + And I run `sh -c "ls /var/log/ubuntu-advantage* | sort -d"` as non-root + Then stdout contains substring: + """ + /var/log/ubuntu-advantage.log + """ + Then stdout does not contain substring: + """ + /var/log/ubuntu-advantage.log.1 + """ + When I run `logrotate --force /etc/logrotate.d/ubuntu-pro-client` with sudo + And I run `sh -c "ls /var/log/ubuntu-advantage* | sort -d"` as non-root + Then stdout contains substring: + """ + /var/log/ubuntu-advantage.log + /var/log/ubuntu-advantage.log.1 + """ + # reset and run logrotate with full config + When I run `rm /var/log/ubuntu-advantage.log.1` with sudo + When I run `pro status` with sudo + And I run `sh -c "ls /var/log/ubuntu-advantage* | sort -d"` as non-root + Then stdout contains substring: + """ + /var/log/ubuntu-advantage.log + """ + Then stdout does not contain substring: + """ + /var/log/ubuntu-advantage.log.1 + """ + # This uses all logrotate config files on the system + When I run `logrotate --force /etc/logrotate.conf` with sudo + And I run `sh -c "ls /var/log/ubuntu-advantage* | sort -d"` as non-root + Then stdout contains substring: + """ + /var/log/ubuntu-advantage.log + /var/log/ubuntu-advantage.log.1 + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/magic_attach.feature ubuntu-advantage-tools-32~16.04/features/magic_attach.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/magic_attach.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/magic_attach.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,54 +1,55 @@ Feature: Magic attach flow related tests - Scenario Outline: Attach using the magic attach flow - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/tmp/response-overlay.json` with the following: - """ - { - "https://contracts.canonical.com/v1/magic-attach": [ - { + Scenario Outline: Attach using the magic attach flow + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/tmp/response-overlay.json` with the following: + """ + { + "https://contracts.canonical.com/v1/magic-attach": [ + { + "code": 200, + "response": { + "userCode": "123", + "token": "testToken", + "expires": "expire-date", + "expiresIn": 2000 + } + }, + { "code": 200, "response": { - "userCode": "123", - "token": "testToken", - "expires": "expire-date", - "expiresIn": 2000 + "userCode": "123", + "token": "testToken", + "expires": "expire-date", + "expiresIn": 2000, + "contractID": "test-contract-id", + "contractToken": "$behave_var{contract_token}" } - }, - { - "code": 200, - "response": { - "userCode": "123", - "token": "testToken", - "expires": "expire-date", - "expiresIn": 2000, - "contractID": "test-contract-id", - "contractToken": "$behave_var{contract_token}" - } - }] - } - """ - And I append the following on uaclient config: - """ - features: - serviceclient_url_responses: "/tmp/response-overlay.json" - """ - And I run `pro attach` with sudo - Then stdout matches regexp: - """ - Initiating attach operation... + }] + } + """ + And I append the following on uaclient config: + """ + features: + serviceclient_url_responses: "/tmp/response-overlay.json" + """ + And I run `pro attach` with sudo + Then stdout matches regexp: + """ + Initiating attach operation... - Please sign in to your Ubuntu Pro account at this link: - https://ubuntu.com/pro/attach - And provide the following code: .*123.* + Please sign in to your Ubuntu Pro account at this link: + https://ubuntu.com/pro/attach + And provide the following code: .*123.* - Attaching the machine... - """ - And the machine is attached + Attaching the machine... + """ + And the machine is attached - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/motd_messages.feature ubuntu-advantage-tools-32~16.04/features/motd_messages.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/motd_messages.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/motd_messages.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,146 +1,143 @@ Feature: MOTD Messages - @uses.config.contract_token - Scenario Outline: Contract update prevents contract expiration messages - Given a `` `` machine with ubuntu-advantage-tools installed - When I apt update - When I attach `contract_token` with sudo - When I update contract to use `effectiveTo` as `$behave_var{today +2}` - When I run `pro refresh messages` with sudo - And I run `run-parts /etc/update-motd.d/` with sudo - Then stdout does not match regexp: - """ - [\w\d.]+ - - CAUTION: Your Ubuntu Pro subscription will expire in 2 days. - Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard to ensure - continued security coverage for your applications. - - [\w\d.]+ - """ - When I update contract to use `effectiveTo` as `$behave_var{today -3}` - When I run `pro refresh messages` with sudo - And I run `run-parts /etc/update-motd.d/` with sudo - Then stdout does not match regexp: - """ - [\w\d.]+ - - CAUTION: Your Ubuntu Pro subscription expired on \d+ \w+ \d+. - Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard to ensure - continued security coverage for your applications. - Your grace period will expire in 11 days. - - [\w\d.]+ - """ - When I update contract to use `effectiveTo` as `$behave_var{today -20}` - When I run `pro refresh messages` with sudo - And I run `run-parts /etc/update-motd.d/` with sudo - Then stdout does not match regexp: - """ - [\w\d.]+ - - \*Your Ubuntu Pro subscription has EXPIRED\* - \d+ additional security updates require Ubuntu Pro with '' enabled. - Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard - - [\w\d.]+ - """ - Examples: ubuntu release - | release | machine_type | service | - | xenial | lxd-container | esm-infra | - | bionic | lxd-container | esm-apps | - - - Scenario Outline: Contract Expiration Messages - Given a `` `` machine with ubuntu-advantage-tools installed - When I apt update - And I apt install `ansible` - And I attach `contract_token` with sudo - And I set the machine token overlay to the following yaml - """ - machineTokenInfo: - contractInfo: - effectiveTo: $behave_var{today +2} - """ - And I run `pro refresh messages` with sudo - And I run `run-parts /etc/update-motd.d/` with sudo - Then stdout matches regexp: - """ - [\w\d.]+ - - CAUTION: Your Ubuntu Pro subscription will expire in 2 days. - Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard to ensure - continued security coverage for your applications. - - """ - When I set the machine token overlay to the following yaml - """ - machineTokenInfo: - contractInfo: - effectiveTo: $behave_var{today -3} - """ - When I run `pro refresh messages` with sudo - And I run `run-parts /etc/update-motd.d/` with sudo - Then stdout matches regexp: - """ - [\w\d.]+ - - CAUTION: Your Ubuntu Pro subscription expired on \d+ \w+ \d+. - Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard to ensure - continued security coverage for your applications. - Your grace period will expire in 11 days. - - """ - When I set the machine token overlay to the following yaml - """ - machineTokenInfo: - contractInfo: - effectiveTo: $behave_var{today -20} - """ - When I run `pro refresh messages` with sudo - And I run `run-parts /etc/update-motd.d/` with sudo - Then stdout matches regexp: - """ - [\w\d.]+ - - \*Your Ubuntu Pro subscription has EXPIRED\* - \d+ additional security updates require Ubuntu Pro with '' enabled. - Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard - - """ - When I apt upgrade - When I run `pro refresh messages` with sudo - And I run `run-parts /etc/update-motd.d/` with sudo - Then stdout matches regexp: - """ - [\w\d.]+ - - \*Your Ubuntu Pro subscription has EXPIRED\* - Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard - - """ - When I create the file `/var/lib/ubuntu-advantage/machine-token-overlay.json` with the following: - """ - { - "machineTokenInfo": { - "contractInfo": { - "effectiveTo": null - } - } - } - """ - When I wait `1` seconds - When I run `pro refresh messages` with sudo - And I run `run-parts /etc/update-motd.d/` with sudo - Then stdout matches regexp: - """ - [\w\d.]+ - - \*Your Ubuntu Pro subscription has EXPIRED\* - Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard - - """ - Examples: ubuntu release - | release | machine_type | service | - | xenial | lxd-container | esm-infra | - | bionic | lxd-container | esm-infra | + @uses.config.contract_token + Scenario Outline: Contract update prevents contract expiration messages + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + When I update contract to use `effectiveTo` as `$behave_var{today +2}` + When I run `pro refresh messages` with sudo + And I run `run-parts /etc/update-motd.d/` with sudo + Then stdout does not match regexp: + """ + [\w\d.]+ + + CAUTION: Your Ubuntu Pro subscription will expire in 2 days. + Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard to ensure + continued security coverage for your applications. + + [\w\d.]+ + """ + When I update contract to use `effectiveTo` as `$behave_var{today -3}` + When I run `pro refresh messages` with sudo + And I run `run-parts /etc/update-motd.d/` with sudo + Then stdout does not match regexp: + """ + [\w\d.]+ + + CAUTION: Your Ubuntu Pro subscription expired on \d+ \w+ \d+. + Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard to ensure + continued security coverage for your applications. + Your grace period will expire in 11 days. + + [\w\d.]+ + """ + When I update contract to use `effectiveTo` as `$behave_var{today -20}` + When I run `pro refresh messages` with sudo + And I run `run-parts /etc/update-motd.d/` with sudo + Then stdout does not match regexp: + """ + [\w\d.]+ + + \*Your Ubuntu Pro subscription has EXPIRED\* + \d+ additional security updates require Ubuntu Pro with '' enabled. + Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard + + [\w\d.]+ + """ + + Examples: ubuntu release + | release | machine_type | service | + | xenial | lxd-container | esm-infra | + | bionic | lxd-container | esm-apps | + | bionic | wsl | esm-apps | + | noble | lxd-container | esm-apps | + + Scenario Outline: Contract Expiration Messages + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt install `ansible hello` + And I attach `contract_token` with sudo + And I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + effectiveTo: $behave_var{today +2} + """ + And I run `pro refresh messages` with sudo + And I run `run-parts /etc/update-motd.d/` with sudo + Then stdout matches regexp: + """ + [\w\d.]+ + + CAUTION: Your Ubuntu Pro subscription will expire in 2 days. + Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard to ensure + continued security coverage for your applications. + """ + When I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + effectiveTo: $behave_var{today -3} + """ + When I run `pro refresh messages` with sudo + And I run `run-parts /etc/update-motd.d/` with sudo + Then stdout matches regexp: + """ + [\w\d.]+ + + CAUTION: Your Ubuntu Pro subscription expired on \d+ \w+ \d+. + Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard to ensure + continued security coverage for your applications. + Your grace period will expire in 11 days. + """ + When I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + effectiveTo: $behave_var{today -20} + """ + When I run `pro refresh messages` with sudo + And I run `run-parts /etc/update-motd.d/` with sudo + Then stdout matches regexp: + """ + [\w\d.]+ + + \*Your Ubuntu Pro subscription has EXPIRED\* + \d+ additional security update(s)? require(s)? Ubuntu Pro with '' enabled. + Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard + """ + When I apt upgrade + When I run `pro refresh messages` with sudo + And I run `run-parts /etc/update-motd.d/` with sudo + Then stdout matches regexp: + """ + [\w\d.]+ + + \*Your Ubuntu Pro subscription has EXPIRED\* + Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard + """ + When I create the file `/var/lib/ubuntu-advantage/machine-token-overlay.json` with the following: + """ + { + "machineTokenInfo": { + "contractInfo": { + "effectiveTo": null + } + } + } + """ + When I wait `1` seconds + When I run `pro refresh messages` with sudo + And I run `run-parts /etc/update-motd.d/` with sudo + Then stdout matches regexp: + """ + [\w\d.]+ + + \*Your Ubuntu Pro subscription has EXPIRED\* + Renew your subscription at https:\/\/ubuntu.com\/pro\/dashboard + """ + + Examples: ubuntu release + | release | machine_type | service | + | xenial | lxd-container | esm-infra | + | bionic | lxd-container | esm-infra | + | bionic | wsl | esm-infra | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/proxy_config.feature ubuntu-advantage-tools-32~16.04/features/proxy_config.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/proxy_config.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/proxy_config.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,1302 +1,1311 @@ @uses.config.contract_token Feature: Proxy configuration - @slow - Scenario Outline: Attach command when proxy is configured for uaclient - Given a `` `` machine with ubuntu-advantage-tools installed - Given a `focal` `lxd-container` machine named `proxy` - When I apt install `squid` on the `proxy` machine - And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: - """ - dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_access allow all - """ - And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine - And I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - And I run `pro config set https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting snap proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT api.snapcraft.io.* - """ - When I run `pro config set http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting snap proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*HEAD http://api.snapcraft.io.* - """ - When I attach `contract_token` with sudo and options `--no-auto-enable` - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT contracts.canonical.com.* - """ - And the machine is attached - When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - And I run `pro config set ua_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting UA-scoped APT proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*HEAD http://archive.ubuntu.com.* - """ - When I run `pro config set ua_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting UA-scoped APT proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT esm.ubuntu.com.* - """ - Then I verify that files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` - When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo - Then stdout matches regexp: - """ - /\* - \* Autogenerated by ubuntu-advantage-tools - \* Do not edit this file directly - \* - \* To change what ubuntu-advantage-tools sets, use the `pro config set` - \* or the `pro config unset` commands to set/unset either: - \* global_apt_http_proxy and global_apt_https_proxy - \* for a global apt proxy - \* or - \* ua_apt_http_proxy and ua_apt_https_proxy - \* for an apt proxy that only applies to Ubuntu Pro related repos. - \*/ - Acquire::http::Proxy::esm.ubuntu.com \".*\"; - Acquire::https::Proxy::esm.ubuntu.com \".*\"; - """ - When I apt update - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - CONNECT esm.ubuntu.com:443 - """ - Then stdout does not match regexp: - """ - .*GET.*ubuntu.com/ubuntu/dists.* - """ - Then stdout does not match regexp: - """ - .*GET.*archive.ubuntu.com.* - """ - Then stdout does not match regexp: - """ - .*GET.*security.ubuntu.com.* - """ - When I run `pro config unset ua_apt_http_proxy` with sudo - And I run `pro config unset ua_apt_https_proxy` with sudo - And I run `pro refresh config` with sudo - Then I verify that no files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "ua_apt_http_proxy": "invalidurl", - "ua_apt_https_proxy": "invalidurls" - } - """ - And I verify that running `pro refresh config` `with sudo` exits `1` - Then stderr matches regexp: - """ - \"invalidurl\" is not a valid url. Not setting as proxy. - """ - When I verify that running `pro config set http_proxy=http://host:port` `with sudo` exits `1` - Then stderr matches regexp: - """ - \"http://host:port\" is not a valid url. Not setting as proxy - """ - When I apt install `python3-pycurl` - And I verify that running `pro config set ua_apt_https_proxy=https://localhost:12345` `with sudo` exits `1` - Then stderr matches regexp: - """ - \"https://localhost:12345\" is not working. Not setting as proxy. - """ - When I run `pro config set ua_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - And I run `pro config set ua_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo - Then stdout matches regexp: - """ - /\* - \* Autogenerated by ubuntu-advantage-tools - \* Do not edit this file directly - \* - \* To change what ubuntu-advantage-tools sets, use the `pro config set` - \* or the `pro config unset` commands to set/unset either: - \* global_apt_http_proxy and global_apt_https_proxy - \* for a global apt proxy - \* or - \* ua_apt_http_proxy and ua_apt_https_proxy - \* for an apt proxy that only applies to Ubuntu Pro related repos. - \*/ - Acquire::http::Proxy::esm.ubuntu.com \".*\"; - Acquire::https::Proxy::esm.ubuntu.com \".*\"; - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - @slow - Scenario Outline: Attach command when proxy is configured - Given a `` `` machine with ubuntu-advantage-tools installed - Given a `focal` `lxd-container` machine named `proxy` - When I apt install `squid` on the `proxy` machine - And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: - """ - dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_access allow all - """ - And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine - And I run `pro config set http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - And I run `pro config set https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT api.snapcraft.io.* - """ - When I attach `contract_token` with sudo - Then stdout matches regexp: - """ - Setting snap proxy - """ - Then stdout matches regexp: - """ - Setting Livepatch proxy - """ - When I run `canonical-livepatch config check-interval=0` with sudo - And I run `canonical-livepatch refresh` with sudo - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT contracts.canonical.com.* - """ - And stdout matches regexp: - """ - .*CONNECT api.snapcraft.io:443.* - """ - When I run `sleep 120` as non-root - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT livepatch.canonical.com:443.* - """ - When I run `pro refresh config` with sudo - Then I will see the following on stdout: - """ - Setting snap proxy - Setting Livepatch proxy - Successfully processed your pro configuration. - """ - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "http_proxy": "", - "https_proxy": "" - } - """ - And I run `pro refresh config` with sudo - Then I will see the following on stdout: - """ - No proxy set in config; however, proxy is configured for: snap, livepatch. - See https://canonical-ubuntu-pro-client.readthedocs-hosted.com/en/latest/howtoguides/configure_proxies.html for more information on pro proxy configuration. - - Successfully processed your pro configuration. - """ - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "http_proxy": "invalidurl", - "https_proxy": "invalidurls" - } - """ - And I apt install `python3-pycurl` - And I verify that running `pro refresh config` `with sudo` exits `1` - Then stderr matches regexp: - """ - \"invalidurl\" is not a valid url. Not setting as proxy. - """ - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "https_proxy": "https://localhost:12345" - } - """ - And I verify that running `pro refresh config` `with sudo` exits `1` - Then stderr matches regexp: - """ - \"https://localhost:12345\" is not working. Not setting as proxy. - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-vm | - | bionic | lxd-vm | - - @slow - Scenario Outline: Attach command when authenticated proxy is configured for uaclient - Given a `` `` machine with ubuntu-advantage-tools installed - Given a `focal` `lxd-container` machine named `proxy` - When I apt update on the `proxy` machine - And I apt install `squid apache2-utils` on the `proxy` machine - And I run `htpasswd -bc /etc/squid/passwordfile someuser somepassword` `with sudo` on the `proxy` machine - And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: - """ - dns_v4_first on\nauth_param basic program \/usr\/lib\/squid\/basic_ncsa_auth \/etc\/squid\/passwordfile\nacl topsecret proxy_auth REQUIRED\nhttp_access allow topsecret - """ - And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine - And I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - When I run `pro config set https_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting snap proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT api.snapcraft.io.* - """ - When I run `pro config set http_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting snap proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*HEAD http://api.snapcraft.io.* - """ - When I attach `contract_token` with sudo - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT contracts.canonical.com.* - """ - When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - When I run `pro config set ua_apt_http_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting UA-scoped APT proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*HEAD http://archive.ubuntu.com.* - """ - When I run `pro config set ua_apt_https_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting UA-scoped APT proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT esm.ubuntu.com.* - """ - When I run `pro refresh config` with sudo - And I apt update - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - CONNECT esm.ubuntu.com:443 - """ - Then stdout does not match regexp: - """ - .*GET.*ubuntu.com/ubuntu/dists.* - """ - Then stdout does not match regexp: - """ - .*GET.*archive.ubuntu.com.* - """ - Then stdout does not match regexp: - """ - .*GET.*security.ubuntu.com.* - """ - And I verify that running `pro config set ua_apt_https_proxy=http://wronguser:wrongpassword@$behave_var{machine-ip proxy}:3128` `with sudo` exits `1` - Then stderr matches regexp: - """ - \"http://wronguser:wrongpassword@.*:3128\" is not working. Not setting as proxy. - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - @slow - Scenario Outline: Attach command when authenticated proxy is configured - Given a `` `` machine with ubuntu-advantage-tools installed - Given a `focal` `lxd-container` machine named `proxy` - When I apt update on the `proxy` machine - And I apt install `squid apache2-utils` on the `proxy` machine - And I run `htpasswd -bc /etc/squid/passwordfile someuser somepassword` `with sudo` on the `proxy` machine - And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: - """ - dns_v4_first on\nauth_param basic program \/usr\/lib\/squid\/basic_ncsa_auth \/etc\/squid\/passwordfile\nacl topsecret proxy_auth REQUIRED\nhttp_access allow topsecret - """ - And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine - And I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - And I run `pro config set http_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo - And I run `pro config set https_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo - And I attach `contract_token` with sudo - Then stdout matches regexp: - """ - Setting snap proxy - """ - Then stdout matches regexp: - """ - Setting Livepatch proxy - """ - When I run `canonical-livepatch config check-interval=0` with sudo - And I run `canonical-livepatch refresh` with sudo - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT contracts.canonical.com.* - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT api.snapcraft.io:443.* - """ - When I run `sleep 120` as non-root - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT livepatch.canonical.com:443.* - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-vm | - | bionic | lxd-vm | - - @slow - Scenario Outline: Attach command when proxy is configured manually via conf file for uaclient - Given a `` `` machine with ubuntu-advantage-tools installed - Given a `focal` `lxd-container` machine named `proxy` - When I apt install `squid` on the `proxy` machine - And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: - """ - dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_access allow all - """ - And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "http_proxy": "http://$behave_var{machine-ip proxy}:3128", - "https_proxy": "http://$behave_var{machine-ip proxy}:3128" - } - """ - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - # We need this for the route command - And I attach `contract_token` with sudo and options `--no-auto-enable` - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT contracts.canonical.com.* - """ - And the machine is attached - When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "ua_apt_http_proxy": "http://$behave_var{machine-ip proxy}:3128", - "ua_apt_https_proxy": "http://$behave_var{machine-ip proxy}:3128" - } - """ - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - Then I verify that no files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` - When I run `pro refresh config` with sudo - Then stdout matches regexp: - """ - Setting UA-scoped APT proxy - """ - Then I verify that files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` - When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo - Then stdout matches regexp: - """ - /\* - \* Autogenerated by ubuntu-advantage-tools - \* Do not edit this file directly - \* - \* To change what ubuntu-advantage-tools sets, use the `pro config set` - \* or the `pro config unset` commands to set/unset either: - \* global_apt_http_proxy and global_apt_https_proxy - \* for a global apt proxy - \* or - \* ua_apt_http_proxy and ua_apt_https_proxy - \* for an apt proxy that only applies to Ubuntu Pro related repos. - \*/ - Acquire::http::Proxy::esm.ubuntu.com \".*\"; - Acquire::https::Proxy::esm.ubuntu.com \".*\"; - """ - When I apt update - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - CONNECT esm.ubuntu.com:443 - """ - Then stdout does not match regexp: - """ - .*GET.*ubuntu.com/ubuntu/dists.* - """ - Then stdout does not match regexp: - """ - .*GET.*archive.ubuntu.com.* - """ - Then stdout does not match regexp: - """ - .*GET.*security.ubuntu.com.* - """ - When I run `pro config unset ua_apt_http_proxy` with sudo - And I run `pro config unset ua_apt_https_proxy` with sudo - And I run `pro refresh config` with sudo - Then I verify that no files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "ua_apt_http_proxy": "invalidurl", - "ua_apt_https_proxy": "invalidurls" - } - """ - And I verify that running `pro refresh config` `with sudo` exits `1` - Then stderr matches regexp: - """ - \"invalidurl\" is not a valid url. Not setting as proxy. - """ - When I verify that running `pro config set http_proxy=http://host:port` `with sudo` exits `1` - Then stderr matches regexp: - """ - \"http://host:port\" is not a valid url. Not setting as proxy - """ - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "ua_apt_https_proxy": "https://localhost:12345" - } - """ - And I apt install `python3-pycurl` - And I verify that running `pro refresh config` `with sudo` exits `1` - Then stderr matches regexp: - """ - \"https://localhost:12345\" is not working. Not setting as proxy. - """ - When I run `pro config set ua_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - And I run `pro config set ua_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo - Then stdout matches regexp: - """ - /\* - \* Autogenerated by ubuntu-advantage-tools - \* Do not edit this file directly - \* - \* To change what ubuntu-advantage-tools sets, use the `pro config set` - \* or the `pro config unset` commands to set/unset either: - \* global_apt_http_proxy and global_apt_https_proxy - \* for a global apt proxy - \* or - \* ua_apt_http_proxy and ua_apt_https_proxy - \* for an apt proxy that only applies to Ubuntu Pro related repos. - \*/ - Acquire::http::Proxy::esm.ubuntu.com \".*\"; - Acquire::https::Proxy::esm.ubuntu.com \".*\"; - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - @slow - Scenario Outline: Attach command when authenticated proxy is configured manually for uaclient - Given a `` `` machine with ubuntu-advantage-tools installed - Given a `focal` `lxd-container` machine named `proxy` - When I apt update on the `proxy` machine - And I apt install `squid apache2-utils` on the `proxy` machine - And I run `htpasswd -bc /etc/squid/passwordfile someuser somepassword` `with sudo` on the `proxy` machine - And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: - """ - dns_v4_first on\nauth_param basic program \/usr\/lib\/squid\/basic_ncsa_auth \/etc\/squid\/passwordfile\nacl topsecret proxy_auth REQUIRED\nhttp_access allow topsecret - """ - And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "http_proxy": "http://someuser:somepassword@$behave_var{machine-ip proxy}:3128", - "https_proxy": "http://someuser:somepassword@$behave_var{machine-ip proxy}:3128" - } - """ - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - And I attach `contract_token` with sudo - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT contracts.canonical.com.* - """ - When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "ua_apt_http_proxy": "http://someuser:somepassword@$behave_var{machine-ip proxy}:3128", - "ua_apt_https_proxy": "http://someuser:somepassword@$behave_var{machine-ip proxy}:3128" - } - """ - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - And I run `pro refresh config` with sudo - And I apt update - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - CONNECT esm.ubuntu.com:443 - """ - Then stdout does not match regexp: - """ - .*GET.*ubuntu.com/ubuntu/dists.* - """ - Then stdout does not match regexp: - """ - .*GET.*archive.ubuntu.com.* - """ - Then stdout does not match regexp: - """ - .*GET.*security.ubuntu.com.* - """ - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "ua_apt_https_proxy": "http://wronguser:wrongpassword@$behave_var{machine-ip proxy}:3128" - } - """ - And I apt install `python3-pycurl` - And I verify that running `pro refresh config` `with sudo` exits `1` - Then stderr matches regexp: - """ - \"http://wronguser:wrongpassword@.*:3128\" is not working. Not setting as proxy. - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - @slow - Scenario Outline: Attach command when proxy is configured globally - Given a `` `` machine with ubuntu-advantage-tools installed - Given a `focal` `lxd-container` machine named `proxy` - When I apt install `squid` on the `proxy` machine - And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: - """ - dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_access allow all - """ - And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine - And I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - And I run `pro config set https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting snap proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT api.snapcraft.io.* - """ - When I run `pro config set http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting snap proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*HEAD http://api.snapcraft.io.* - """ - # We need this for the route command - When I apt install `net-tools` - # We will guarantee that the machine will only use the proxy when - # running the pro commands - And I run `route del default` with sudo - And I attach `contract_token` with sudo and options `--no-auto-enable` - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT contracts.canonical.com.* - """ - And the machine is attached - When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - And I run `pro config set global_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting global APT proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*HEAD http://archive.ubuntu.com.* - """ - When I run `pro config set global_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting global APT proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT esm.ubuntu.com.* - """ - # TODO No longer empty, needs researching - Then I verify that files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` - When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo - Then stdout matches regexp: - """ - /\* - \* Autogenerated by ubuntu-advantage-tools - \* Do not edit this file directly - \* - \* To change what ubuntu-advantage-tools sets, use the `pro config set` - \* or the `pro config unset` commands to set/unset either: - \* global_apt_http_proxy and global_apt_https_proxy - \* for a global apt proxy - \* or - \* ua_apt_http_proxy and ua_apt_https_proxy - \* for an apt proxy that only applies to Ubuntu Pro related repos. - \*/ - Acquire::http::Proxy \".*\"; - Acquire::https::Proxy \".*\"; - """ - When I apt update - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - CONNECT esm.ubuntu.com:443 - """ - Then stdout matches regexp: - """ - .*GET.*ubuntu.com/ubuntu/dists.* - """ - Then stdout matches regexp: - """ - .*GET.*archive.ubuntu.com.* - """ - Then stdout matches regexp: - """ - .*GET.*security.ubuntu.com.* - """ - When I run `pro config unset global_apt_http_proxy` with sudo - And I run `pro config unset global_apt_https_proxy` with sudo - And I run `pro refresh config` with sudo - Then I verify that no files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "global_apt_http_proxy": "invalidurl", - "global_https_proxy": "invalidurls" - } - """ - And I verify that running `pro refresh config` `with sudo` exits `1` - Then stderr matches regexp: - """ - \"invalidurl\" is not a valid url. Not setting as proxy. - """ - When I verify that running `pro config set http_proxy=http://host:port` `with sudo` exits `1` - Then stderr matches regexp: - """ - \"http://host:port\" is not a valid url. Not setting as proxy - """ - When I apt install `python3-pycurl` - And I verify that running `pro config set global_apt_https_proxy=https://localhost:12345` `with sudo` exits `1` - Then stderr matches regexp: - """ - \"https://localhost:12345\" is not working. Not setting as proxy. - """ - When I run `pro config set global_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - And I run `pro config set global_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo - Then stdout matches regexp: - """ - /\* - \* Autogenerated by ubuntu-advantage-tools - \* Do not edit this file directly - \* - \* To change what ubuntu-advantage-tools sets, use the `pro config set` - \* or the `pro config unset` commands to set/unset either: - \* global_apt_http_proxy and global_apt_https_proxy - \* for a global apt proxy - \* or - \* ua_apt_http_proxy and ua_apt_https_proxy - \* for an apt proxy that only applies to Ubuntu Pro related repos. - \*/ - Acquire::http::Proxy \".*\"; - Acquire::https::Proxy \".*\"; - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - @slow - Scenario Outline: Attach command when authenticated proxy is configured globally - Given a `` `` machine with ubuntu-advantage-tools installed - Given a `focal` `lxd-container` machine named `proxy` - When I apt update on the `proxy` machine - And I apt install `squid apache2-utils` on the `proxy` machine - And I run `htpasswd -bc /etc/squid/passwordfile someuser somepassword` `with sudo` on the `proxy` machine - And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: - """ - dns_v4_first on\nauth_param basic program \/usr\/lib\/squid\/basic_ncsa_auth \/etc\/squid\/passwordfile\nacl topsecret proxy_auth REQUIRED\nhttp_access allow topsecret - """ - And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine - And I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - When I run `pro config set https_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting snap proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT api.snapcraft.io.* - """ - When I run `pro config set http_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting snap proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*HEAD http://api.snapcraft.io.* - """ - When I apt install `net-tools` - # We will guarantee that the machine will only use the proxy when - # running the pro commands - And I run `route del default` with sudo - And I attach `contract_token` with sudo and options `--no-auto-enable` - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT contracts.canonical.com.* - """ - When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - When I run `pro config set global_apt_http_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting global APT proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*HEAD http://archive.ubuntu.com.* - """ - When I run `pro config set global_apt_https_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Setting global APT proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT esm.ubuntu.com.* - """ - When I run `pro refresh config` with sudo - And I apt update - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - CONNECT esm.ubuntu.com:443 - """ - Then stdout matches regexp: - """ - .*GET.*ubuntu.com/ubuntu/dists.* - """ - Then stdout matches regexp: - """ - .*GET.*archive.ubuntu.com.* - """ - Then stdout matches regexp: - """ - .*GET.*security.ubuntu.com.* - """ - And I verify that running `pro config set global_apt_https_proxy=http://wronguser:wrongpassword@$behave_var{machine-ip proxy}:3128` `with sudo` exits `1` - Then stderr matches regexp: - """ - \"http://wronguser:wrongpassword@.*:3128\" is not working. Not setting as proxy. - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - @slow - Scenario Outline: Get warning when configuring global or uaclient proxy - Given a `` `` machine with ubuntu-advantage-tools installed - Given a `focal` `lxd-container` machine named `proxy` - When I apt install `squid` on the `proxy` machine - And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: - """ - dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_access allow all - """ - And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine - And I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - And I run `pro config set global_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - And I run `pro config set global_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - Then I verify that files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` - When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo - Then stdout matches regexp: - """ - /\* - \* Autogenerated by ubuntu-advantage-tools - \* Do not edit this file directly - \* - \* To change what ubuntu-advantage-tools sets, use the `pro config set` - \* or the `pro config unset` commands to set/unset either: - \* global_apt_http_proxy and global_apt_https_proxy - \* for a global apt proxy - \* or - \* ua_apt_http_proxy and ua_apt_https_proxy - \* for an apt proxy that only applies to Ubuntu Pro related repos. - \*/ - Acquire::http::Proxy \".*\"; - Acquire::https::Proxy \".*\"; - """ - When I apt update - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - CONNECT esm.ubuntu.com:443 - """ - Then stdout matches regexp: - """ - .*GET.*ubuntu.com/ubuntu/dists.* - """ - Then stdout matches regexp: - """ - .*GET.*archive.ubuntu.com.* - """ - Then stdout matches regexp: - """ - .*GET.*security.ubuntu.com.* - """ - When I run `pro config set ua_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Warning: Setting the pro scoped apt proxy will overwrite the global apt - proxy previously set via `pro config`. - """ - When I run `pro config set ua_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - Then stdout does not match regexp: - """ - Warning: Setting the pro scoped apt proxy will overwrite the global apt - proxy previously set via `pro config`. - """ - When I run `pro config show` with sudo - Then stdout matches regexp: - """ - global_apt_http_proxy +None - """ - Then stdout matches regexp: - """ - global_apt_https_proxy +None - """ - When I run `pro config unset ua_apt_http_proxy` with sudo - And I run `pro config unset ua_apt_https_proxy` with sudo - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "apt_http_proxy": "http://$behave_var{machine-ip proxy}:3128" - } - """ - And I verify that running `pro refresh config` `with sudo` exits `0` - Then stdout matches regexp: - """ - Using deprecated "apt_http_proxy" config field. - Please migrate to using "global_apt_http_proxy" - """ - When I run `pro config show` with sudo - Then stdout matches regexp: - """ - global_apt_http_proxy +http://$behave_var{machine-ip proxy}:3128 - """ - Then stdout matches regexp: - """ - apt_http_proxy +None - """ - When I run `pro config unset global_apt_http_proxy` with sudo - And I run `pro config unset global_apt_https_proxy` with sudo - And I run `pro config unset ua_apt_http_proxy` with sudo - And I run `pro config unset ua_apt_https_proxy` with sudo - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "global_apt_http_proxy": "http://$behave_var{machine-ip proxy}:3128", - "ua_apt_http_proxy": "http://$behave_var{machine-ip proxy}:3128" - } - """ - And I verify that running `pro refresh config` `with sudo` exits `1` - Then stderr matches regexp: - """ - Error: Setting global apt proxy and pro scoped apt proxy - at the same time is unsupported. - Cancelling config process operation. - """ - When I run `pro config show` with sudo - Then stdout matches regexp: - """ - global_apt_http_proxy +http://$behave_var{machine-ip proxy}:3128 - """ - Then stdout matches regexp: - """ - ua_apt_http_proxy +http://$behave_var{machine-ip proxy}:3128 - """ - Then I verify that no files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` - When I run `pro config set global_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - And I run `pro config set global_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo - Then stdout matches regexp: - """ - /\* - \* Autogenerated by ubuntu-advantage-tools - \* Do not edit this file directly - \* - \* To change what ubuntu-advantage-tools sets, use the `pro config set` - \* or the `pro config unset` commands to set/unset either: - \* global_apt_http_proxy and global_apt_https_proxy - \* for a global apt proxy - \* or - \* ua_apt_http_proxy and ua_apt_https_proxy - \* for an apt proxy that only applies to Ubuntu Pro related repos. - \*/ - Acquire::http::Proxy \".*\"; - Acquire::https::Proxy \".*\"; - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - @slow - Scenario Outline: apt_http(s)_proxy still works - Given a `` `` machine with ubuntu-advantage-tools installed - Given a `focal` `lxd-container` machine named `proxy` - When I apt install `squid` on the `proxy` machine - And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: - """ - dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_access allow all - """ - And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine - And I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - And I attach `contract_token` with sudo - Then the machine is attached - When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - And I run `pro config set apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Warning: apt_http_proxy has been renamed to global_apt_http_proxy. - Setting global APT proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*HEAD http://archive.ubuntu.com.* - """ - When I run `pro config set apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - Then stdout matches regexp: - """ - Warning: apt_https_proxy has been renamed to global_apt_https_proxy. - Setting global APT proxy - """ - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - CONNECT esm.ubuntu.com - """ - Then I verify that files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` - When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo - Then stdout matches regexp: - """ - /\* - \* Autogenerated by ubuntu-advantage-tools - \* Do not edit this file directly - \* - \* To change what ubuntu-advantage-tools sets, use the `pro config set` - \* or the `pro config unset` commands to set/unset either: - \* global_apt_http_proxy and global_apt_https_proxy - \* for a global apt proxy - \* or - \* ua_apt_http_proxy and ua_apt_https_proxy - \* for an apt proxy that only applies to Ubuntu Pro related repos. - \*/ - Acquire::http::Proxy \".*:3128\"; - Acquire::https::Proxy \".*:3128\"; - """ - When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - When I apt update - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - CONNECT esm.ubuntu.com:443 - """ - Then stdout matches regexp: - """ - GET.*ubuntu.com/ubuntu/dists - """ - Then stdout matches regexp: - """ - GET.*archive.ubuntu.com - """ - Then stdout matches regexp: - """ - GET.*security.ubuntu.com - """ - When I run `pro config unset apt_http_proxy` with sudo - And I run `pro config unset apt_https_proxy` with sudo - And I run `pro refresh config` with sudo - Then I verify that no files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` - When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: - """ - { - "apt_http_proxy": "http://$behave_var{machine-ip proxy}:3128", - "apt_https_proxy": "http://$behave_var{machine-ip proxy}:3128" - } - """ - When I run `pro refresh config` with sudo - Then stdout matches regexp: - """ - Using deprecated "apt_http_proxy" config field. - Please migrate to using "global_apt_http_proxy" - - Using deprecated "apt_https_proxy" config field. - Please migrate to using "global_apt_https_proxy" - - Setting global APT proxy - Successfully processed your pro configuration. - """ - When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo - Then stdout matches regexp: - """ - /\* - \* Autogenerated by ubuntu-advantage-tools - \* Do not edit this file directly - \* - \* To change what ubuntu-advantage-tools sets, use the `pro config set` - \* or the `pro config unset` commands to set/unset either: - \* global_apt_http_proxy and global_apt_https_proxy - \* for a global apt proxy - \* or - \* ua_apt_http_proxy and ua_apt_https_proxy - \* for an apt proxy that only applies to Ubuntu Pro related repos. - \*/ - Acquire::http::Proxy \".*:3128\"; - Acquire::https::Proxy \".*:3128\"; - """ - When I apt install `python3-pycurl` - And I verify that running `pro config set apt_https_proxy=https://localhost:12345` `with sudo` exits `1` - Then stdout matches regexp: - """ - Warning: apt_https_proxy has been renamed to global_apt_https_proxy. - """ - Then stderr matches regexp: - """ - \"https://localhost:12345\" is not working. Not setting as proxy. - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - @slow - Scenario: Enable realtime kernel through proxy on a machine with no internet - Given a `jammy` `lxd-vm` machine with ubuntu-advantage-tools installed - When I disable any internet connection on the machine - Given a `focal` `lxd-container` machine named `proxy` - When I apt install `squid` on the `proxy` machine - And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: - """ - dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_access allow all - """ - And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine - And I run `pro config set https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - And I run `pro config set http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - And I run `pro config set global_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - And I run `pro config set global_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo - And I attach `contract_token` with sudo - Then I verify that `esm-apps` is enabled - And I verify that `esm-infra` is enabled - When I run `pro disable livepatch --assume-yes` with sudo - When I run `pro enable realtime-kernel` `with sudo` and stdin `y` - Then stdout contains substring: - """ - Installing Real-time kernel packages - Real-time kernel enabled - A reboot is required to complete install. - """ - - Scenario Outline: Support HTTPS-in-HTTPS proxies - Given a `` `` machine with ubuntu-advantage-tools installed - - # set up a HTTPS proxy - Given a `jammy` `` machine named `proxy` - When I apt update on the `proxy` machine - And I apt install `openssl libssl-dev ssl-cert squid-openssl apache2-utils` on the `proxy` machine - And I run `openssl req -newkey rsa:4096 -x509 -sha256 -days 3650 -nodes -out ca.crt -keyout ca.key -subj "/C=CN/ST=BJ/O=STS/CN=CA"` `with sudo` on the `proxy` machine - And I run `openssl genrsa -out $behave_var{machine-name proxy}.lxd.key` `with sudo` on the `proxy` machine - And I run `openssl req -new -key $behave_var{machine-name proxy}.lxd.key -out $behave_var{machine-name proxy}.lxd.csr -subj "/C=CN/ST=BJ/O=STS/CN=$behave_var{machine-name proxy}.lxd"` `with sudo` on the `proxy` machine - And I create the file `/home/ubuntu/data.ext` on the `proxy` machine with the following - """ - authorityKeyIdentifier=keyid,issuer - basicConstraints=CA:FALSE - subjectAltName = @alt_names - [alt_names] - DNS.1 = $behave_var{machine-name proxy}.lxd - """ - And I run `openssl x509 -req -in $behave_var{machine-name proxy}.lxd.csr -out $behave_var{machine-name proxy}.lxd.crt -sha256 -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -extfile data.ext` `with sudo` on the `proxy` machine - And I run `htpasswd -bc /etc/squid/passwordfile someuser somepassword` `with sudo` on the `proxy` machine - And I create the file `/etc/squid/squid.conf` on the `proxy` machine with the following: - """ - dns_v4_first on - auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwordfile - acl topsecret proxy_auth REQUIRED - acl all src 0.0.0.0/0 - http_access allow topsecret - http_access deny all - via off - forwarded_for delete - https_port 0.0.0.0:3129 cert=/home/ubuntu/$behave_var{machine-name proxy}.lxd.crt key=/home/ubuntu/$behave_var{machine-name proxy}.lxd.key - """ - And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine - - # Configure system-under-test to trust the HTTPS proxy - When I move `proxy` `/home/ubuntu/ca.crt` to `system-under-test` `/usr/local/share/ca-certificates/ca.crt` - And I run `update-ca-certificates` with sudo - And I run `systemctl restart snapd.service` with sudo - - # error message to install pycurl - When I verify that running `pro config set https_proxy=https://someuser:somepassword@$behave_var{machine-name proxy}.lxd:3129` `with sudo` exits `1` - Then I will see the following on stderr - """ - To use an HTTPS proxy for HTTPS connections, please install pycurl with `apt install python3-pycurl` - """ - - When I apt install `python3-pycurl` - - # error message on failed auth - When I verify that running `pro config set https_proxy=https://someuser:wrongpassword@$behave_var{machine-name proxy}.lxd:3129` `with sudo` exits `1` - Then I will see the following on stderr - """ - Proxy authentication failed - """ - - When I apt remove `ca-certificates` - And I run `rm -f /etc/ssl/certs/ca-certificates.crt` with sudo - And I verify that running `pro config set https_proxy=https://someuser:somepassword@$behave_var{machine-name proxy}.lxd:3129` `with sudo` exits `1` - Then stderr matches regexp: - """ - Failed to access URL: https://.* - Cannot verify certificate of server - Please install "ca-certificates" and try again. - """ - - When I apt install `ca-certificates` - And I run `update-ca-certificates` with sudo - And I run `pro config set https_proxy=https://someuser:somepassword@$behave_var{machine-name proxy}.lxd:3129` with sudo - And I run `pro config set ua_apt_https_proxy=https://someuser:somepassword@$behave_var{machine-name proxy}.lxd:3129` with sudo - - When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I attach `contract_token` with sudo and options `--no-auto-enable` - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout contains substring - """ - CONNECT contracts.canonical.com:443 someuser - """ - And stdout does not contain substring - """ - error:transaction-end-before-headers - """ - - When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I run `pro enable esm-infra` with sudo - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout contains substring - """ - CONNECT esm.ubuntu.com:443 someuser - """ - And stdout does not contain substring - """ - error:transaction-end-before-headers - """ - - # Pre-install canonical-livepatch to tell it to trust the cert - When I apt install `snapd` - And I run `snap install canonical-livepatch` with sudo - And I run shell command `canonical-livepatch config ca-certs=@stdin < /usr/local/share/ca-certificates/ca.crt` with sudo - - When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I run `pro enable livepatch` with sudo - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout contains substring - """ - CONNECT api.snapcraft.io:443 someuser - """ - And stdout does not contain substring - """ - error:transaction-end-before-headers - """ - - When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine - And I apt install `hello` - And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout contains substring - """ - CONNECT esm.ubuntu.com:443 someuser - """ - - Examples: ubuntu release - | release | machine_type | - | bionic | lxd-vm | - | focal | lxd-vm | - | jammy | lxd-vm | - | mantic | lxd-vm | + @slow + Scenario Outline: Attach command when proxy is configured for uaclient + Given a `` `` machine with ubuntu-advantage-tools installed + Given a `focal` `lxd-container` machine named `proxy` + When I apt install `squid` on the `proxy` machine + And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: + """ + dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_access allow all + """ + And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine + And I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + And I run `pro config set https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting snap proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT api.snapcraft.io.* + """ + When I run `pro config set http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting snap proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*HEAD http://api.snapcraft.io.* + """ + When I attach `contract_token` with sudo and options `--no-auto-enable` + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT contracts.canonical.com.* + """ + And the machine is attached + When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + And I run `pro config set ua_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting UA-scoped APT proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*HEAD http://archive.ubuntu.com.* + """ + When I run `pro config set ua_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting UA-scoped APT proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT esm.ubuntu.com.* + """ + Then I verify that files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` + When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo + Then stdout matches regexp: + """ + /\* + \* Autogenerated by ubuntu-pro-client + \* Do not edit this file directly + \* + \* To change what ubuntu-pro-client sets, use the `pro config set` + \* or the `pro config unset` commands to set/unset either: + \* global_apt_http_proxy and global_apt_https_proxy + \* for a global apt proxy + \* or + \* ua_apt_http_proxy and ua_apt_https_proxy + \* for an apt proxy that only applies to Ubuntu Pro related repos. + \*/ + Acquire::http::Proxy::esm.ubuntu.com \".*\"; + Acquire::https::Proxy::esm.ubuntu.com \".*\"; + """ + When I apt update + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + CONNECT esm.ubuntu.com:443 + """ + Then stdout does not match regexp: + """ + .*GET.*ubuntu.com/ubuntu/dists.* + """ + Then stdout does not match regexp: + """ + .*GET.*archive.ubuntu.com.* + """ + Then stdout does not match regexp: + """ + .*GET.*security.ubuntu.com.* + """ + When I run `pro config unset ua_apt_http_proxy` with sudo + And I run `pro config unset ua_apt_https_proxy` with sudo + And I run `pro refresh config` with sudo + Then I verify that no files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "ua_apt_http_proxy": "invalidurl", + "ua_apt_https_proxy": "invalidurls" + } + """ + And I verify that running `pro refresh config` `with sudo` exits `1` + Then stderr matches regexp: + """ + \"invalidurl\" is not a valid url. Not setting as proxy. + """ + When I verify that running `pro config set http_proxy=http://host:port` `with sudo` exits `1` + Then stderr matches regexp: + """ + \"http://host:port\" is not a valid url. Not setting as proxy + """ + When I apt install `python3-pycurl` + And I verify that running `pro config set ua_apt_https_proxy=https://localhost:12345` `with sudo` exits `1` + Then stderr matches regexp: + """ + \"https://localhost:12345\" is not working. Not setting as proxy. + """ + When I run `pro config set ua_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + And I run `pro config set ua_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo + Then stdout matches regexp: + """ + /\* + \* Autogenerated by ubuntu-pro-client + \* Do not edit this file directly + \* + \* To change what ubuntu-pro-client sets, use the `pro config set` + \* or the `pro config unset` commands to set/unset either: + \* global_apt_http_proxy and global_apt_https_proxy + \* for a global apt proxy + \* or + \* ua_apt_http_proxy and ua_apt_https_proxy + \* for an apt proxy that only applies to Ubuntu Pro related repos. + \*/ + Acquire::http::Proxy::esm.ubuntu.com \".*\"; + Acquire::https::Proxy::esm.ubuntu.com \".*\"; + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + @slow + Scenario Outline: Attach command when proxy is configured + Given a `` `` machine with ubuntu-advantage-tools installed + Given a `focal` `lxd-container` machine named `proxy` + When I apt install `squid` on the `proxy` machine + And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: + """ + dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_access allow all + """ + And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine + And I run `pro config set http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + And I run `pro config set https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT api.snapcraft.io.* + """ + When I attach `contract_token` with sudo + Then stdout matches regexp: + """ + Setting snap proxy + """ + Then stdout matches regexp: + """ + Setting Livepatch proxy + """ + When I run `canonical-livepatch config check-interval=0` with sudo + And I run `canonical-livepatch refresh` with sudo + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT contracts.canonical.com.* + """ + And stdout matches regexp: + """ + .*CONNECT api.snapcraft.io:443.* + """ + When I run `sleep 120` as non-root + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT livepatch.canonical.com:443.* + """ + When I run `pro refresh config` with sudo + Then I will see the following on stdout: + """ + Setting snap proxy + Setting Livepatch proxy + Successfully processed your pro configuration. + """ + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "http_proxy": "", + "https_proxy": "" + } + """ + And I run `pro refresh config` with sudo + Then I will see the following on stdout: + """ + No proxy set in config; however, proxy is configured for: snap, livepatch. + See https://canonical-ubuntu-pro-client.readthedocs-hosted.com/en/latest/howtoguides/configure_proxies.html for more information on pro proxy configuration. + + Successfully processed your pro configuration. + """ + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "http_proxy": "invalidurl", + "https_proxy": "invalidurls" + } + """ + And I apt install `python3-pycurl` + And I verify that running `pro refresh config` `with sudo` exits `1` + Then stderr matches regexp: + """ + \"invalidurl\" is not a valid url. Not setting as proxy. + """ + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "https_proxy": "https://localhost:12345" + } + """ + And I verify that running `pro refresh config` `with sudo` exits `1` + Then stderr matches regexp: + """ + \"https://localhost:12345\" is not working. Not setting as proxy. + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-vm | + | bionic | lxd-vm | + + @slow + Scenario Outline: Attach and config show command when authenticated proxy is configured for uaclient + Given a `` `` machine with ubuntu-advantage-tools installed + Given a `focal` `lxd-container` machine named `proxy` + When I apt install `squid apache2-utils` on the `proxy` machine + And I run `htpasswd -bc /etc/squid/passwordfile someuser somepassword` `with sudo` on the `proxy` machine + And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: + """ + dns_v4_first on\nauth_param basic program \/usr\/lib\/squid\/basic_ncsa_auth \/etc\/squid\/passwordfile\nacl topsecret proxy_auth REQUIRED\nhttp_access allow topsecret + """ + And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine + And I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + When I run `pro config set https_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting snap proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT api.snapcraft.io.* + """ + When I run `pro config set http_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting snap proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*HEAD http://api.snapcraft.io.* + """ + When I attach `contract_token` with sudo + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT contracts.canonical.com.* + """ + When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + When I run `pro config set ua_apt_http_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting UA-scoped APT proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*HEAD http://archive.ubuntu.com.* + """ + When I run `pro config set ua_apt_https_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting UA-scoped APT proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT esm.ubuntu.com.* + """ + When I run `pro refresh config` with sudo + And I apt update + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + CONNECT esm.ubuntu.com:443 + """ + Then stdout does not match regexp: + """ + .*GET.*ubuntu.com/ubuntu/dists.* + """ + Then stdout does not match regexp: + """ + .*GET.*archive.ubuntu.com.* + """ + Then stdout does not match regexp: + """ + .*GET.*security.ubuntu.com.* + """ + And I verify that running `pro config set ua_apt_https_proxy=http://wronguser:wrongpassword@$behave_var{machine-ip proxy}:3128` `with sudo` exits `1` + Then stderr matches regexp: + """ + \"http://wronguser:wrongpassword@.*:3128\" is not working. Not setting as proxy. + """ + When I run `pro config set http_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo + When I run `pro config show` with sudo + Then stdout matches regexp: + """ + http_proxy http://someuser:somepassword@$behave_var{machine-ip proxy}:3128 + """ + When I run `pro config show` as non-root + Then stdout matches regexp: + """ + http_proxy + """ + When I run `pro status --all` as non-root + And I run `cat /var/lib/ubuntu-advantage/status.json` with sudo + Then stdout matches regexp: + """ + \"http_proxy\": \"\" + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + @slow + Scenario Outline: Attach command when authenticated proxy is configured + Given a `` `` machine with ubuntu-advantage-tools installed + Given a `focal` `lxd-container` machine named `proxy` + When I apt install `squid apache2-utils` on the `proxy` machine + And I run `htpasswd -bc /etc/squid/passwordfile someuser somepassword` `with sudo` on the `proxy` machine + And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: + """ + dns_v4_first on\nauth_param basic program \/usr\/lib\/squid\/basic_ncsa_auth \/etc\/squid\/passwordfile\nacl topsecret proxy_auth REQUIRED\nhttp_access allow topsecret + """ + And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine + And I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + And I run `pro config set http_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo + And I run `pro config set https_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo + And I attach `contract_token` with sudo + Then stdout matches regexp: + """ + Setting snap proxy + """ + Then stdout matches regexp: + """ + Setting Livepatch proxy + """ + When I run `canonical-livepatch config check-interval=0` with sudo + And I run `canonical-livepatch refresh` with sudo + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT contracts.canonical.com.* + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT api.snapcraft.io:443.* + """ + When I run `sleep 120` as non-root + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT livepatch.canonical.com:443.* + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-vm | + | bionic | lxd-vm | + + @slow + Scenario Outline: Attach command when proxy is configured manually via conf file for uaclient + Given a `` `` machine with ubuntu-advantage-tools installed + Given a `focal` `lxd-container` machine named `proxy` + When I apt install `squid` on the `proxy` machine + And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: + """ + dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_access allow all + """ + And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "http_proxy": "http://$behave_var{machine-ip proxy}:3128", + "https_proxy": "http://$behave_var{machine-ip proxy}:3128" + } + """ + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + # We need this for the route command + And I attach `contract_token` with sudo and options `--no-auto-enable` + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT contracts.canonical.com.* + """ + And the machine is attached + When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "ua_apt_http_proxy": "http://$behave_var{machine-ip proxy}:3128", + "ua_apt_https_proxy": "http://$behave_var{machine-ip proxy}:3128" + } + """ + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + Then I verify that no files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` + When I run `pro refresh config` with sudo + Then stdout matches regexp: + """ + Setting UA-scoped APT proxy + """ + Then I verify that files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` + When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo + Then stdout matches regexp: + """ + /\* + \* Autogenerated by ubuntu-pro-client + \* Do not edit this file directly + \* + \* To change what ubuntu-pro-client sets, use the `pro config set` + \* or the `pro config unset` commands to set/unset either: + \* global_apt_http_proxy and global_apt_https_proxy + \* for a global apt proxy + \* or + \* ua_apt_http_proxy and ua_apt_https_proxy + \* for an apt proxy that only applies to Ubuntu Pro related repos. + \*/ + Acquire::http::Proxy::esm.ubuntu.com \".*\"; + Acquire::https::Proxy::esm.ubuntu.com \".*\"; + """ + When I apt update + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + CONNECT esm.ubuntu.com:443 + """ + Then stdout does not match regexp: + """ + .*GET.*ubuntu.com/ubuntu/dists.* + """ + Then stdout does not match regexp: + """ + .*GET.*archive.ubuntu.com.* + """ + Then stdout does not match regexp: + """ + .*GET.*security.ubuntu.com.* + """ + When I run `pro config unset ua_apt_http_proxy` with sudo + And I run `pro config unset ua_apt_https_proxy` with sudo + And I run `pro refresh config` with sudo + Then I verify that no files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "ua_apt_http_proxy": "invalidurl", + "ua_apt_https_proxy": "invalidurls" + } + """ + And I verify that running `pro refresh config` `with sudo` exits `1` + Then stderr matches regexp: + """ + \"invalidurl\" is not a valid url. Not setting as proxy. + """ + When I verify that running `pro config set http_proxy=http://host:port` `with sudo` exits `1` + Then stderr matches regexp: + """ + \"http://host:port\" is not a valid url. Not setting as proxy + """ + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "ua_apt_https_proxy": "https://localhost:12345" + } + """ + And I apt install `python3-pycurl` + And I verify that running `pro refresh config` `with sudo` exits `1` + Then stderr matches regexp: + """ + \"https://localhost:12345\" is not working. Not setting as proxy. + """ + When I run `pro config set ua_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + And I run `pro config set ua_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo + Then stdout matches regexp: + """ + /\* + \* Autogenerated by ubuntu-pro-client + \* Do not edit this file directly + \* + \* To change what ubuntu-pro-client sets, use the `pro config set` + \* or the `pro config unset` commands to set/unset either: + \* global_apt_http_proxy and global_apt_https_proxy + \* for a global apt proxy + \* or + \* ua_apt_http_proxy and ua_apt_https_proxy + \* for an apt proxy that only applies to Ubuntu Pro related repos. + \*/ + Acquire::http::Proxy::esm.ubuntu.com \".*\"; + Acquire::https::Proxy::esm.ubuntu.com \".*\"; + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + @slow + Scenario Outline: Attach command when authenticated proxy is configured manually for uaclient + Given a `` `` machine with ubuntu-advantage-tools installed + Given a `focal` `lxd-container` machine named `proxy` + When I apt install `squid apache2-utils` on the `proxy` machine + And I run `htpasswd -bc /etc/squid/passwordfile someuser somepassword` `with sudo` on the `proxy` machine + And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: + """ + dns_v4_first on\nauth_param basic program \/usr\/lib\/squid\/basic_ncsa_auth \/etc\/squid\/passwordfile\nacl topsecret proxy_auth REQUIRED\nhttp_access allow topsecret + """ + And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "http_proxy": "http://someuser:somepassword@$behave_var{machine-ip proxy}:3128", + "https_proxy": "http://someuser:somepassword@$behave_var{machine-ip proxy}:3128" + } + """ + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + And I attach `contract_token` with sudo + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT contracts.canonical.com.* + """ + When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "ua_apt_http_proxy": "http://someuser:somepassword@$behave_var{machine-ip proxy}:3128", + "ua_apt_https_proxy": "http://someuser:somepassword@$behave_var{machine-ip proxy}:3128" + } + """ + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + And I run `pro refresh config` with sudo + And I apt update + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + CONNECT esm.ubuntu.com:443 + """ + Then stdout does not match regexp: + """ + .*GET.*ubuntu.com/ubuntu/dists.* + """ + Then stdout does not match regexp: + """ + .*GET.*archive.ubuntu.com.* + """ + Then stdout does not match regexp: + """ + .*GET.*security.ubuntu.com.* + """ + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "ua_apt_https_proxy": "http://wronguser:wrongpassword@$behave_var{machine-ip proxy}:3128" + } + """ + And I apt install `python3-pycurl` + And I verify that running `pro refresh config` `with sudo` exits `1` + Then stderr matches regexp: + """ + \"http://wronguser:wrongpassword@.*:3128\" is not working. Not setting as proxy. + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + @slow + Scenario Outline: Attach command when proxy is configured globally + Given a `` `` machine with ubuntu-advantage-tools installed + Given a `focal` `lxd-container` machine named `proxy` + When I apt install `squid` on the `proxy` machine + And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: + """ + dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_access allow all + """ + And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine + And I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + And I run `pro config set https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting snap proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT api.snapcraft.io.* + """ + When I run `pro config set http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting snap proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*HEAD http://api.snapcraft.io.* + """ + # We need this for the route command + When I apt install `net-tools` + # We will guarantee that the machine will only use the proxy when + # running the pro commands + And I run `route del default` with sudo + And I attach `contract_token` with sudo and options `--no-auto-enable` + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT contracts.canonical.com.* + """ + And the machine is attached + When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + And I run `pro config set global_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting global APT proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*HEAD http://archive.ubuntu.com.* + """ + When I run `pro config set global_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting global APT proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT esm.ubuntu.com.* + """ + # TODO No longer empty, needs researching + Then I verify that files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` + When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo + Then stdout matches regexp: + """ + /\* + \* Autogenerated by ubuntu-pro-client + \* Do not edit this file directly + \* + \* To change what ubuntu-pro-client sets, use the `pro config set` + \* or the `pro config unset` commands to set/unset either: + \* global_apt_http_proxy and global_apt_https_proxy + \* for a global apt proxy + \* or + \* ua_apt_http_proxy and ua_apt_https_proxy + \* for an apt proxy that only applies to Ubuntu Pro related repos. + \*/ + Acquire::http::Proxy \".*\"; + Acquire::https::Proxy \".*\"; + """ + When I apt update + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + CONNECT esm.ubuntu.com:443 + """ + Then stdout matches regexp: + """ + .*GET.*ubuntu.com/ubuntu/dists.* + """ + Then stdout matches regexp: + """ + .*GET.*archive.ubuntu.com.* + """ + Then stdout matches regexp: + """ + .*GET.*security.ubuntu.com.* + """ + When I run `pro config unset global_apt_http_proxy` with sudo + And I run `pro config unset global_apt_https_proxy` with sudo + And I run `pro refresh config` with sudo + Then I verify that no files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "global_apt_http_proxy": "invalidurl", + "global_https_proxy": "invalidurls" + } + """ + And I verify that running `pro refresh config` `with sudo` exits `1` + Then stderr matches regexp: + """ + \"invalidurl\" is not a valid url. Not setting as proxy. + """ + When I verify that running `pro config set http_proxy=http://host:port` `with sudo` exits `1` + Then stderr matches regexp: + """ + \"http://host:port\" is not a valid url. Not setting as proxy + """ + When I apt install `python3-pycurl` + And I verify that running `pro config set global_apt_https_proxy=https://localhost:12345` `with sudo` exits `1` + Then stderr matches regexp: + """ + \"https://localhost:12345\" is not working. Not setting as proxy. + """ + When I run `pro config set global_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + And I run `pro config set global_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo + Then stdout matches regexp: + """ + /\* + \* Autogenerated by ubuntu-pro-client + \* Do not edit this file directly + \* + \* To change what ubuntu-pro-client sets, use the `pro config set` + \* or the `pro config unset` commands to set/unset either: + \* global_apt_http_proxy and global_apt_https_proxy + \* for a global apt proxy + \* or + \* ua_apt_http_proxy and ua_apt_https_proxy + \* for an apt proxy that only applies to Ubuntu Pro related repos. + \*/ + Acquire::http::Proxy \".*\"; + Acquire::https::Proxy \".*\"; + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + @slow + Scenario Outline: Attach command when authenticated proxy is configured globally + Given a `` `` machine with ubuntu-advantage-tools installed + Given a `focal` `lxd-container` machine named `proxy` + When I apt install `squid apache2-utils` on the `proxy` machine + And I run `htpasswd -bc /etc/squid/passwordfile someuser somepassword` `with sudo` on the `proxy` machine + And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: + """ + dns_v4_first on\nauth_param basic program \/usr\/lib\/squid\/basic_ncsa_auth \/etc\/squid\/passwordfile\nacl topsecret proxy_auth REQUIRED\nhttp_access allow topsecret + """ + And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine + And I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + When I run `pro config set https_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting snap proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT api.snapcraft.io.* + """ + When I run `pro config set http_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting snap proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*HEAD http://api.snapcraft.io.* + """ + When I apt install `net-tools` + # We will guarantee that the machine will only use the proxy when + # running the pro commands + And I run `route del default` with sudo + And I attach `contract_token` with sudo and options `--no-auto-enable` + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT contracts.canonical.com.* + """ + When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + When I run `pro config set global_apt_http_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting global APT proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*HEAD http://archive.ubuntu.com.* + """ + When I run `pro config set global_apt_https_proxy=http://someuser:somepassword@$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Setting global APT proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT esm.ubuntu.com.* + """ + When I run `pro refresh config` with sudo + And I apt update + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + CONNECT esm.ubuntu.com:443 + """ + Then stdout matches regexp: + """ + .*GET.*ubuntu.com/ubuntu/dists.* + """ + Then stdout matches regexp: + """ + .*GET.*archive.ubuntu.com.* + """ + Then stdout matches regexp: + """ + .*GET.*security.ubuntu.com.* + """ + And I verify that running `pro config set global_apt_https_proxy=http://wronguser:wrongpassword@$behave_var{machine-ip proxy}:3128` `with sudo` exits `1` + Then stderr matches regexp: + """ + \"http://wronguser:wrongpassword@.*:3128\" is not working. Not setting as proxy. + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + @slow + Scenario Outline: Get warning when configuring global or uaclient proxy + Given a `` `` machine with ubuntu-advantage-tools installed + Given a `focal` `lxd-container` machine named `proxy` + When I apt install `squid` on the `proxy` machine + And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: + """ + dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_access allow all + """ + And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine + And I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + And I run `pro config set global_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + And I run `pro config set global_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + Then I verify that files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` + When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo + Then stdout matches regexp: + """ + /\* + \* Autogenerated by ubuntu-pro-client + \* Do not edit this file directly + \* + \* To change what ubuntu-pro-client sets, use the `pro config set` + \* or the `pro config unset` commands to set/unset either: + \* global_apt_http_proxy and global_apt_https_proxy + \* for a global apt proxy + \* or + \* ua_apt_http_proxy and ua_apt_https_proxy + \* for an apt proxy that only applies to Ubuntu Pro related repos. + \*/ + Acquire::http::Proxy \".*\"; + Acquire::https::Proxy \".*\"; + """ + When I apt update + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + CONNECT esm.ubuntu.com:443 + """ + Then stdout matches regexp: + """ + .*GET.*ubuntu.com/ubuntu/dists.* + """ + Then stdout matches regexp: + """ + .*GET.*archive.ubuntu.com.* + """ + Then stdout matches regexp: + """ + .*GET.*security.ubuntu.com.* + """ + When I run `pro config set ua_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Warning: Setting the pro scoped apt proxy will overwrite the global apt + proxy previously set via `pro config`. + """ + When I run `pro config set ua_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + Then stdout does not match regexp: + """ + Warning: Setting the pro scoped apt proxy will overwrite the global apt + proxy previously set via `pro config`. + """ + When I run `pro config show` with sudo + Then stdout matches regexp: + """ + global_apt_http_proxy +None + """ + Then stdout matches regexp: + """ + global_apt_https_proxy +None + """ + When I run `pro config unset ua_apt_http_proxy` with sudo + And I run `pro config unset ua_apt_https_proxy` with sudo + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "apt_http_proxy": "http://$behave_var{machine-ip proxy}:3128" + } + """ + And I verify that running `pro refresh config` `with sudo` exits `0` + Then stdout matches regexp: + """ + Using deprecated "apt_http_proxy" config field. + Please migrate to using "global_apt_http_proxy" + """ + When I run `pro config show` with sudo + Then stdout matches regexp: + """ + global_apt_http_proxy +http://$behave_var{machine-ip proxy}:3128 + """ + Then stdout matches regexp: + """ + apt_http_proxy +None + """ + When I run `pro config unset global_apt_http_proxy` with sudo + And I run `pro config unset global_apt_https_proxy` with sudo + And I run `pro config unset ua_apt_http_proxy` with sudo + And I run `pro config unset ua_apt_https_proxy` with sudo + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "global_apt_http_proxy": "http://$behave_var{machine-ip proxy}:3128", + "ua_apt_http_proxy": "http://$behave_var{machine-ip proxy}:3128" + } + """ + And I verify that running `pro refresh config` `with sudo` exits `1` + Then stderr matches regexp: + """ + Error: Setting global apt proxy and pro scoped apt proxy + at the same time is unsupported. + Cancelling config process operation. + """ + When I run `pro config show` with sudo + Then stdout matches regexp: + """ + global_apt_http_proxy +http://$behave_var{machine-ip proxy}:3128 + """ + Then stdout matches regexp: + """ + ua_apt_http_proxy +http://$behave_var{machine-ip proxy}:3128 + """ + Then I verify that no files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` + When I run `pro config set global_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + And I run `pro config set global_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo + Then stdout matches regexp: + """ + /\* + \* Autogenerated by ubuntu-pro-client + \* Do not edit this file directly + \* + \* To change what ubuntu-pro-client sets, use the `pro config set` + \* or the `pro config unset` commands to set/unset either: + \* global_apt_http_proxy and global_apt_https_proxy + \* for a global apt proxy + \* or + \* ua_apt_http_proxy and ua_apt_https_proxy + \* for an apt proxy that only applies to Ubuntu Pro related repos. + \*/ + Acquire::http::Proxy \".*\"; + Acquire::https::Proxy \".*\"; + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + @slow + Scenario Outline: apt_http(s)_proxy still works + Given a `` `` machine with ubuntu-advantage-tools installed + Given a `focal` `lxd-container` machine named `proxy` + When I apt install `squid` on the `proxy` machine + And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: + """ + dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_access allow all + """ + And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine + And I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + And I attach `contract_token` with sudo + Then the machine is attached + When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + And I run `pro config set apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Warning: apt_http_proxy has been renamed to global_apt_http_proxy. + Setting global APT proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*HEAD http://archive.ubuntu.com.* + """ + When I run `pro config set apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + Then stdout matches regexp: + """ + Warning: apt_https_proxy has been renamed to global_apt_https_proxy. + Setting global APT proxy + """ + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + CONNECT esm.ubuntu.com + """ + Then I verify that files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` + When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo + Then stdout matches regexp: + """ + /\* + \* Autogenerated by ubuntu-pro-client + \* Do not edit this file directly + \* + \* To change what ubuntu-pro-client sets, use the `pro config set` + \* or the `pro config unset` commands to set/unset either: + \* global_apt_http_proxy and global_apt_https_proxy + \* for a global apt proxy + \* or + \* ua_apt_http_proxy and ua_apt_https_proxy + \* for an apt proxy that only applies to Ubuntu Pro related repos. + \*/ + Acquire::http::Proxy \".*:3128\"; + Acquire::https::Proxy \".*:3128\"; + """ + When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + When I apt update + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + CONNECT esm.ubuntu.com:443 + """ + Then stdout matches regexp: + """ + GET.*ubuntu.com/ubuntu/dists + """ + Then stdout matches regexp: + """ + GET.*archive.ubuntu.com + """ + Then stdout matches regexp: + """ + GET.*security.ubuntu.com + """ + When I run `pro config unset apt_http_proxy` with sudo + And I run `pro config unset apt_https_proxy` with sudo + And I run `pro refresh config` with sudo + Then I verify that no files exist matching `/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` + When I create the file `/var/lib/ubuntu-advantage/user-config.json` with the following: + """ + { + "apt_http_proxy": "http://$behave_var{machine-ip proxy}:3128", + "apt_https_proxy": "http://$behave_var{machine-ip proxy}:3128" + } + """ + When I run `pro refresh config` with sudo + Then stdout matches regexp: + """ + Using deprecated "apt_http_proxy" config field. + Please migrate to using "global_apt_http_proxy" + + Using deprecated "apt_https_proxy" config field. + Please migrate to using "global_apt_https_proxy" + + Setting global APT proxy + Successfully processed your pro configuration. + """ + When I run `cat /etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy` with sudo + Then stdout matches regexp: + """ + /\* + \* Autogenerated by ubuntu-pro-client + \* Do not edit this file directly + \* + \* To change what ubuntu-pro-client sets, use the `pro config set` + \* or the `pro config unset` commands to set/unset either: + \* global_apt_http_proxy and global_apt_https_proxy + \* for a global apt proxy + \* or + \* ua_apt_http_proxy and ua_apt_https_proxy + \* for an apt proxy that only applies to Ubuntu Pro related repos. + \*/ + Acquire::http::Proxy \".*:3128\"; + Acquire::https::Proxy \".*:3128\"; + """ + When I apt install `python3-pycurl` + And I verify that running `pro config set apt_https_proxy=https://localhost:12345` `with sudo` exits `1` + Then stdout matches regexp: + """ + Warning: apt_https_proxy has been renamed to global_apt_https_proxy. + """ + Then stderr matches regexp: + """ + \"https://localhost:12345\" is not working. Not setting as proxy. + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + @slow + Scenario: Enable realtime kernel through proxy on a machine with no internet + Given a `jammy` `lxd-vm` machine with ubuntu-advantage-tools installed + When I disable any internet connection on the machine + Given a `focal` `lxd-container` machine named `proxy` + When I apt install `squid` on the `proxy` machine + And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: + """ + dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_access allow all + """ + And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine + And I run `pro config set https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + And I run `pro config set http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + And I run `pro config set global_apt_http_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + And I run `pro config set global_apt_https_proxy=http://$behave_var{machine-ip proxy}:3128` with sudo + And I attach `contract_token` with sudo + Then I verify that `esm-apps` is enabled + And I verify that `esm-infra` is enabled + When I run `pro disable livepatch --assume-yes` with sudo + When I run `pro enable realtime-kernel` `with sudo` and stdin `y` + Then stdout contains substring: + """ + Installing Real-time kernel packages + Real-time kernel enabled + A reboot is required to complete install. + """ + + Scenario Outline: Support HTTPS-in-HTTPS proxies + Given a `` `` machine with ubuntu-advantage-tools installed + # set up a HTTPS proxy + Given a `jammy` `` machine named `proxy` + When I apt install `openssl libssl-dev ssl-cert squid-openssl apache2-utils` on the `proxy` machine + And I run `openssl req -newkey rsa:4096 -x509 -sha256 -days 3650 -nodes -out ca.crt -keyout ca.key -subj "/C=CN/ST=BJ/O=STS/CN=CA"` `with sudo` on the `proxy` machine + And I run `openssl genrsa -out $behave_var{machine-name proxy}.lxd.key` `with sudo` on the `proxy` machine + And I run `openssl req -new -key $behave_var{machine-name proxy}.lxd.key -out $behave_var{machine-name proxy}.lxd.csr -subj "/C=CN/ST=BJ/O=STS/CN=$behave_var{machine-name proxy}.lxd"` `with sudo` on the `proxy` machine + And I create the file `/home/ubuntu/data.ext` on the `proxy` machine with the following + """ + authorityKeyIdentifier=keyid,issuer + basicConstraints=CA:FALSE + subjectAltName = @alt_names + [alt_names] + DNS.1 = $behave_var{machine-name proxy}.lxd + """ + And I run `openssl x509 -req -in $behave_var{machine-name proxy}.lxd.csr -out $behave_var{machine-name proxy}.lxd.crt -sha256 -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -extfile data.ext` `with sudo` on the `proxy` machine + And I run `htpasswd -bc /etc/squid/passwordfile someuser somepassword` `with sudo` on the `proxy` machine + And I create the file `/etc/squid/squid.conf` on the `proxy` machine with the following: + """ + dns_v4_first on + auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwordfile + acl topsecret proxy_auth REQUIRED + acl all src 0.0.0.0/0 + http_access allow topsecret + http_access deny all + via off + forwarded_for delete + https_port 0.0.0.0:3129 cert=/home/ubuntu/$behave_var{machine-name proxy}.lxd.crt key=/home/ubuntu/$behave_var{machine-name proxy}.lxd.key + """ + And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine + # Configure system-under-test to trust the HTTPS proxy + When I move `proxy` `/home/ubuntu/ca.crt` to `system-under-test` `/usr/local/share/ca-certificates/ca.crt` + And I run `update-ca-certificates` with sudo + And I run `systemctl restart snapd.service` with sudo + # error message to install pycurl + When I verify that running `pro config set https_proxy=https://someuser:somepassword@$behave_var{machine-name proxy}.lxd:3129` `with sudo` exits `1` + Then I will see the following on stderr + """ + To use an HTTPS proxy for HTTPS connections, please install pycurl with `apt install python3-pycurl` + """ + When I apt install `python3-pycurl` + # error message on failed auth + When I verify that running `pro config set https_proxy=https://someuser:wrongpassword@$behave_var{machine-name proxy}.lxd:3129` `with sudo` exits `1` + Then I will see the following on stderr + """ + Proxy authentication failed + """ + When I apt remove `ca-certificates` + And I run `rm -f /etc/ssl/certs/ca-certificates.crt` with sudo + And I verify that running `pro config set https_proxy=https://someuser:somepassword@$behave_var{machine-name proxy}.lxd:3129` `with sudo` exits `1` + Then stderr matches regexp: + """ + Failed to access URL: https://.* + Cannot verify certificate of server + Please install "ca-certificates" and try again. + """ + When I apt install `ca-certificates` + And I run `update-ca-certificates` with sudo + And I run `pro config set https_proxy=https://someuser:somepassword@$behave_var{machine-name proxy}.lxd:3129` with sudo + And I run `pro config set ua_apt_https_proxy=https://someuser:somepassword@$behave_var{machine-name proxy}.lxd:3129` with sudo + When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I attach `contract_token` with sudo and options `--no-auto-enable` + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout contains substring + """ + CONNECT contracts.canonical.com:443 someuser + """ + And stdout does not contain substring + """ + error:transaction-end-before-headers + """ + When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I run `pro enable esm-infra` with sudo + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout contains substring + """ + CONNECT esm.ubuntu.com:443 someuser + """ + And stdout does not contain substring + """ + error:transaction-end-before-headers + """ + # Pre-install canonical-livepatch to tell it to trust the cert + When I apt install `snapd` + And I run `snap install canonical-livepatch` with sudo + And I run shell command `canonical-livepatch config ca-certs=@stdin < /usr/local/share/ca-certificates/ca.crt` with sudo + When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I run `pro enable livepatch` with sudo + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout contains substring + """ + CONNECT api.snapcraft.io:443 someuser + """ + And stdout does not contain substring + """ + error:transaction-end-before-headers + """ + When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine + And I apt install `hello` + And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout contains substring + """ + CONNECT esm.ubuntu.com:443 someuser + """ + + Examples: ubuntu release + | release | machine_type | + | bionic | lxd-vm | + | focal | lxd-vm | + | jammy | lxd-vm | + | noble | lxd-vm | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/realtime_kernel.feature ubuntu-advantage-tools-32~16.04/features/realtime_kernel.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/realtime_kernel.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/realtime_kernel.feature 2024-05-10 17:07:05.000000000 +0000 @@ -1,385 +1,390 @@ @uses.config.contract_token Feature: Enable command behaviour when attached to an Ubuntu Pro subscription - Scenario Outline: Enable Real-time kernel service in a container - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo and options `--no-auto-enable` - Then I verify that running `pro enable realtime-kernel` `as non-root` exits `1` - And I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - Then I verify that running `pro enable realtime-kernel --beta` `with sudo` exits `1` - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Cannot install Real-time kernel on a container. - """ - Examples: ubuntu release - | release | machine_type | - | jammy | lxd-container | - - Scenario Outline: Enable Real-time kernel service on unsupported release - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo and options `--no-auto-enable` - Then I verify that running `pro enable realtime-kernel` `as non-root` exits `1` - And I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - Then I verify that running `pro enable realtime-kernel --beta` `with sudo` exits `1` - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Real-time kernel is not available for Ubuntu (). - """ - Examples: ubuntu release - | release | machine_type | version | full_name | - | xenial | lxd-vm | 16.04 LTS | Xenial Xerus | - | bionic | lxd-vm | 18.04 LTS | Bionic Beaver | - | focal | lxd-vm | 20.04 LTS | Focal Fossa | - - Scenario Outline: Enable Real-time kernel service - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo and options `--no-auto-enable` - Then I verify that running `pro enable realtime-kernel` `as non-root` exits `1` - And I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - When I run `pro enable realtime-kernel` `with sudo` and stdin `y` - Then stdout matches regexp: - """ - One moment, checking your subscription first - The Real-time kernel is an Ubuntu kernel with PREEMPT_RT patches integrated. - - .*This will change your kernel. To revert to your original kernel, you will need - to make the change manually..* - - Do you want to continue\? \[ default = Yes \]: \(Y/n\) Updating Real-time kernel package lists - Updating standard Ubuntu package lists - Installing Real-time kernel packages - Real-time kernel enabled - A reboot is required to complete install\. - """ - When I run `apt-cache policy ubuntu-realtime` as non-root - Then stdout does not match regexp: - """ - .*Installed: \(none\) - """ - And stdout matches regexp: - """ - \s* 500 https://esm.ubuntu.com/realtime/ubuntu /main amd64 Packages - """ - When I run `pro api u.pro.status.enabled_services.v1` as non-root - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"enabled_services": \[{"name": "realtime-kernel", "variant_enabled": true, "variant_name": "generic"}\]}, "meta": {"environment_vars": \[\]}, "type": "EnabledServices"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I verify that running `pro enable realtime-kernel` `with sudo` exits `1` - Then stdout matches regexp - """ - One moment, checking your subscription first - Real-time kernel is already enabled. - See: sudo pro status - """ - When I reboot the machine - When I run `uname -r` as non-root - Then stdout matches regexp: - """ - realtime - """ - When I run `pro disable realtime-kernel` `with sudo` and stdin `y` - Then stdout matches regexp: - """ - This will remove the boot order preference for the Real-time kernel and - disable updates to the Real-time kernel. - - This will NOT fully remove the kernel from your system. - - After this operation is complete you must: - - Ensure a different kernel is installed and configured to boot - - Reboot into that kernel - - Fully remove the realtime kernel packages from your system - - This might look something like `apt remove linux\*realtime`, - but you must ensure this is correct before running it. - """ - When I run `apt-cache policy ubuntu-realtime` as non-root - Then stdout contains substring - """ - Installed: (none) - """ - When I verify that running `pro enable realtime-kernel --access-only --variant nvidia-tegra` `with sudo` exits `1` - Then I will see the following on stderr: - """ - Error: Cannot use --access-only together with --variant. - """ - - # Test one variant - # We need to disable this job before adding the overlay, because we might - # write the machine token to disk with the override content - When I run `pro config set update_messaging_timer=0` with sudo - And I run `pro enable realtime-kernel --assume-yes` with sudo - And I run `pro status --all` as non-root - Then stdout matches regexp: - """ - realtime-kernel yes +enabled +Ubuntu kernel with PREEMPT_RT patches integrated - ├ generic yes +enabled +Generic version of the RT kernel \(default\) - └ intel-iotg yes +disabled +RT kernel optimized for Intel IOTG platform - """ - When I run `pro api u.pro.status.enabled_services.v1` as non-root - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"enabled_services": \[{"name": "realtime-kernel", "variant_enabled": true, "variant_name": "generic"}\]}, "meta": {"environment_vars": \[\]}, "type": "EnabledServices"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I run `pro enable realtime-kernel --variant intel-iotg` `with sudo` and stdin `y\ny\n` - Then stdout contains substring: - """ - Real-time Intel IOTG Kernel cannot be enabled with Real-time kernel. - Disable Real-time kernel and proceed to enable Real-time Intel IOTG Kernel? (y/N) - """ - When I run `apt-cache policy ubuntu-intel-iot-realtime` as non-root - Then stdout does not match regexp: - """ - Installed: \(none\) - """ - When I run `pro status --all` as non-root - Then stdout matches regexp: - """ - realtime-kernel yes +enabled +Ubuntu kernel with PREEMPT_RT patches integrated - ├ generic yes +disabled +Generic version of the RT kernel \(default\) - └ intel-iotg yes +enabled +RT kernel optimized for Intel IOTG platform - """ - When I run `pro api u.pro.status.enabled_services.v1` as non-root - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"enabled_services": \[{"name": "realtime-kernel", "variant_enabled": true, "variant_name": "intel-iotg"}\]}, "meta": {"environment_vars": \[\]}, "type": "EnabledServices"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ - When I reboot the machine - And I run `uname -r` as non-root - Then stdout matches regexp: - """ - intel - """ - When I run `pro enable realtime-kernel --variant generic` `with sudo` and stdin `y\ny\n` - And I run `pro status --all` as non-root - Then stdout matches regexp: - """ - realtime-kernel yes +enabled +Ubuntu kernel with PREEMPT_RT patches integrated - ├ generic yes +enabled +Generic version of the RT kernel \(default\) - └ intel-iotg yes +disabled +RT kernel optimized for Intel IOTG platform - """ - When I run `pro enable realtime-kernel --variant intel-iotg` `with sudo` and stdin `y\ny\n` - And I run `pro status --all` as non-root - Then stdout matches regexp: - """ - realtime-kernel yes +enabled +Ubuntu kernel with PREEMPT_RT patches integrated - ├ generic yes +disabled +Generic version of the RT kernel \(default\) - └ intel-iotg yes +enabled +RT kernel optimized for Intel IOTG platform - """ - When I verify that running `pro enable realtime-kernel` `with sudo` exits `1` - Then stdout contains substring: - """ - Real-time kernel is already enabled. - """ - When I run `pro disable realtime-kernel --assume-yes` with sudo - When I run `apt-cache policy ubuntu-intel-iot-realtime` as non-root - Then stdout contains substring: - """ - Installed: (none) - """ - - # Test multiple variants - When I set the machine token overlay to the following yaml - """ - machineTokenInfo: - contractInfo: - resourceEntitlements: - - type: realtime-kernel - overrides: - - selector: - variant: nvidia-tegra - directives: - additionalPackages: - - nvidia-prime - - selector: - variant: rpi - directives: - additionalPackages: - - raspi-config - """ - When I run `pro enable realtime-kernel --variant nvidia-tegra` `with sudo` and stdin `y` - Then stdout matches regexp: - """ - One moment, checking your subscription first - The Real-time kernel is an Ubuntu kernel with PREEMPT_RT patches integrated. - - .*This will change your kernel. To revert to your original kernel, you will need - to make the change manually..* - - Do you want to continue\? \[ default = Yes \]: \(Y/n\) Updating Real-time NVIDIA Tegra Kernel package lists - Updating standard Ubuntu package lists - Installing Real-time NVIDIA Tegra Kernel packages - Real-time NVIDIA Tegra Kernel enabled - """ - When I run `pro status` as non-root - Then stdout matches regexp: - """ - realtime-kernel\* yes +enabled +Ubuntu kernel with PREEMPT_RT patches integrated - usg +yes +disabled +Security compliance and audit tools - - \* Service has variants - """ - Then stdout contains substring: - """ - For a list of all Ubuntu Pro services and variants, run 'pro status --all' - """ - When I run `pro status --all` as non-root - Then stdout matches regexp: - """ - realtime-kernel yes +enabled +Ubuntu kernel with PREEMPT_RT patches integrated - ├ generic yes +disabled +Generic version of the RT kernel \(default\) - ├ intel-iotg yes +disabled +RT kernel optimized for Intel IOTG platform - ├ nvidia-tegra yes +enabled +RT kernel optimized for NVIDIA Tegra platform - └ rpi yes +disabled +24.04 Real-time kernel optimised for Raspberry Pi - """ - When I verify that running `pro enable realtime-kernel --variant intel-iotg` `with sudo` and stdin `N` exits `1` - Then stdout matches regexp: - """ - Real-time Intel IOTG Kernel cannot be enabled with Real-time NVIDIA Tegra Kernel. - Disable Real-time NVIDIA Tegra Kernel and proceed to enable Real-time Intel IOTG Kernel\? \(y/N\) - """ - And stdout matches regexp: - """ - Cannot enable Real-time Intel IOTG Kernel when Real-time NVIDIA Tegra Kernel is enabled. - """ - When I run `pro enable realtime-kernel --variant rpi --assume-yes` with sudo - When I run `pro status --all` as non-root - Then stdout matches regexp: - """ - realtime-kernel yes +enabled +Ubuntu kernel with PREEMPT_RT patches integrated - ├ generic yes +disabled +Generic version of the RT kernel \(default\) - ├ intel-iotg yes +disabled +RT kernel optimized for Intel IOTG platform - ├ nvidia-tegra yes +disabled +RT kernel optimized for NVIDIA Tegra platform - └ rpi yes +enabled +24.04 Real-time kernel optimised for Raspberry Pi - """ - When I run `pro help realtime-kernel` as non-root - Then I will see the following on stdout: - """ - Name: - realtime-kernel - - Entitled: - yes - - Status: - enabled - - Help: - The Real-time kernel is an Ubuntu kernel with PREEMPT_RT patches integrated. It - services latency-dependent use cases by providing deterministic response times. - The Real-time kernel meets stringent preemption specifications and is suitable - for telco applications and dedicated devices in industrial automation and - robotics. The Real-time kernel is currently incompatible with FIPS and - Livepatch. - - Variants: - - * generic: Generic version of the RT kernel (default) - * intel-iotg: RT kernel optimized for Intel IOTG platform - * nvidia-tegra: RT kernel optimized for NVIDIA Tegra platform - * rpi: 24.04 Real-time kernel optimised for Raspberry Pi - """ - When I run `pro disable realtime-kernel` `with sudo` and stdin `y` - Then stdout matches regexp: - """ - This will remove the boot order preference for the Real-time kernel and - disable updates to the Real-time kernel. - - This will NOT fully remove the kernel from your system. - - After this operation is complete you must: - - Ensure a different kernel is installed and configured to boot - - Reboot into that kernel - - Fully remove the realtime kernel packages from your system - - This might look something like `apt remove linux\*realtime`, - but you must ensure this is correct before running it. - """ - When I run `pro status` as non-root - Then stdout matches regexp: - """ - realtime-kernel\* +yes +disabled +Ubuntu kernel with PREEMPT_RT patches integrated - """ - When I run `pro status --all` as non-root - Then stdout matches regexp: - """ - realtime-kernel yes +disabled +Ubuntu kernel with PREEMPT_RT patches integrated - ├ generic yes +disabled +Generic version of the RT kernel \(default\) - ├ intel-iotg yes +disabled +RT kernel optimized for Intel IOTG platform - ├ nvidia-tegra yes +disabled +RT kernel optimized for NVIDIA Tegra platform - └ rpi yes +disabled +24.04 Real-time kernel optimised for Raspberry Pi - """ - When I verify that running `pro enable realtime-kernel --variant nonexistent` `with sudo` exits `1` - Then I will see the following on stdout: - """ - One moment, checking your subscription first - could not find entitlement named "nonexistent" - """ - When I run `pro detach --assume-yes` with sudo - And I run `pro status` as non-root - Then stdout matches regexp: - """ - realtime-kernel +yes +Ubuntu kernel with PREEMPT_RT patches integrated - """ - When I run `pro status --all` as non-root - Then stdout matches regexp: - """ - realtime-kernel +yes +Ubuntu kernel with PREEMPT_RT patches integrated - """ - And stdout does not match regexp: - """ - nvidia-tegra - """ - And stdout does not match regexp: - """ - intel-iotg - """ - - Examples: ubuntu release - | release | machine_type | - | jammy | lxd-vm | - - Scenario Outline: Enable Real-time kernel service access-only - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo and options `--no-auto-enable` - When I run `pro enable realtime-kernel --access-only` with sudo - Then stdout matches regexp: - """ - One moment, checking your subscription first - Updating Real-time kernel package lists - Skipping installing packages: ubuntu-realtime - Real-time kernel access enabled - """ - Then stdout does not match regexp: - """ - A reboot is required to complete install. - """ - When I run `apt-cache policy ubuntu-realtime` as non-root - Then stdout matches regexp: - """ - .*Installed: \(none\) - """ - And stdout matches regexp: - """ - \s* 500 https://esm.ubuntu.com/realtime/ubuntu /main amd64 Packages - """ - When I apt install `ubuntu-realtime` - When I reboot the machine - When I run `uname -r` as non-root - Then stdout matches regexp: - """ - realtime - """ - Examples: ubuntu release - | release | machine_type | - | jammy | lxd-vm | + Scenario Outline: Enable Real-time kernel service in a container + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo and options `--no-auto-enable` + Then I verify that running `pro enable realtime-kernel` `as non-root` exits `1` + And I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + Then I verify that running `pro enable realtime-kernel --beta` `with sudo` exits `1` + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Cannot install Real-time kernel on a container. + Could not enable Real-time kernel. + """ + + Examples: ubuntu release + | release | machine_type | + | jammy | lxd-container | + + Scenario Outline: Enable Real-time kernel service on unsupported release + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo and options `--no-auto-enable` + Then I verify that running `pro enable realtime-kernel` `as non-root` exits `1` + And I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + Then I verify that running `pro enable realtime-kernel --beta` `with sudo` exits `1` + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Real-time kernel is not available for Ubuntu (). + Could not enable Real-time kernel. + """ + + Examples: ubuntu release + | release | machine_type | version | full_name | + | xenial | lxd-vm | 16.04 LTS | Xenial Xerus | + | bionic | lxd-vm | 18.04 LTS | Bionic Beaver | + | focal | lxd-vm | 20.04 LTS | Focal Fossa | + + Scenario Outline: Enable Real-time kernel service + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo and options `--no-auto-enable` + Then I verify that running `pro enable realtime-kernel` `as non-root` exits `1` + And I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + When I run `pro enable realtime-kernel` `with sudo` and stdin `y` + Then stdout matches regexp: + """ + One moment, checking your subscription first + The Real-time kernel is an Ubuntu kernel with PREEMPT_RT patches integrated. + + .*This will change your kernel. To revert to your original kernel, you will need + to make the change manually..* + + Do you want to continue\? \[ default = Yes \]: \(Y/n\) Configuring APT access to Real-time kernel + Updating Real-time kernel package lists + Updating standard Ubuntu package lists + Installing Real-time kernel packages + Real-time kernel enabled + A reboot is required to complete install\. + """ + When I run `apt-cache policy ubuntu-realtime` as non-root + Then stdout does not match regexp: + """ + .*Installed: \(none\) + """ + And stdout matches regexp: + """ + \s* 500 https://esm.ubuntu.com/realtime/ubuntu /main amd64 Packages + """ + When I run `pro api u.pro.status.enabled_services.v1` as non-root + Then stdout matches regexp: + """ + {"_schema_version": "v1", "data": {"attributes": {"enabled_services": \[{"name": "realtime-kernel", "variant_enabled": true, "variant_name": "generic"}\]}, "meta": {"environment_vars": \[\]}, "type": "EnabledServices"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} + """ + When I verify that running `pro enable realtime-kernel` `with sudo` exits `1` + Then stdout matches regexp + """ + One moment, checking your subscription first + Real-time kernel is already enabled - nothing to do. + See: sudo pro status + """ + When I reboot the machine + When I run `uname -r` as non-root + Then stdout matches regexp: + """ + realtime + """ + When I run `pro disable realtime-kernel` `with sudo` and stdin `y` + Then stdout matches regexp: + """ + This will remove the boot order preference for the Real-time kernel and + disable updates to the Real-time kernel. + + This will NOT fully remove the kernel from your system. + + After this operation is complete you must: + - Ensure a different kernel is installed and configured to boot + - Reboot into that kernel + - Fully remove the realtime kernel packages from your system + - This might look something like `apt remove linux\*realtime`, + but you must ensure this is correct before running it. + """ + When I run `apt-cache policy ubuntu-realtime` as non-root + Then stdout contains substring + """ + Installed: (none) + """ + When I verify that running `pro enable realtime-kernel --access-only --variant nvidia-tegra` `with sudo` exits `1` + Then I will see the following on stderr: + """ + Error: Cannot use --access-only together with --variant. + """ + # Test one variant + # We need to disable this job before adding the overlay, because we might + # write the machine token to disk with the override content + When I run `pro config set update_messaging_timer=0` with sudo + And I run `pro enable realtime-kernel --assume-yes` with sudo + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + realtime-kernel yes +enabled +Ubuntu kernel with PREEMPT_RT patches integrated + ├ generic yes +enabled +Generic version of the RT kernel \(default\) + └ intel-iotg yes +disabled +RT kernel optimized for Intel IOTG platform + """ + When I run `pro api u.pro.status.enabled_services.v1` as non-root + Then stdout matches regexp: + """ + {"_schema_version": "v1", "data": {"attributes": {"enabled_services": \[{"name": "realtime-kernel", "variant_enabled": true, "variant_name": "generic"}\]}, "meta": {"environment_vars": \[\]}, "type": "EnabledServices"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} + """ + When I run `pro enable realtime-kernel --variant intel-iotg` `with sudo` and stdin `y\ny\n` + Then stdout contains substring: + """ + Real-time Intel IOTG Kernel cannot be enabled with Real-time kernel. + Disable Real-time kernel and proceed to enable Real-time Intel IOTG Kernel? (y/N) + """ + When I run `apt-cache policy ubuntu-intel-iot-realtime` as non-root + Then stdout does not match regexp: + """ + Installed: \(none\) + """ + When I run `pro status --all` as non-root + Then stdout matches regexp: + """ + realtime-kernel yes +enabled +Ubuntu kernel with PREEMPT_RT patches integrated + ├ generic yes +disabled +Generic version of the RT kernel \(default\) + └ intel-iotg yes +enabled +RT kernel optimized for Intel IOTG platform + """ + When I run `pro api u.pro.status.enabled_services.v1` as non-root + Then stdout matches regexp: + """ + {"_schema_version": "v1", "data": {"attributes": {"enabled_services": \[{"name": "realtime-kernel", "variant_enabled": true, "variant_name": "intel-iotg"}\]}, "meta": {"environment_vars": \[\]}, "type": "EnabledServices"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} + """ + When I reboot the machine + And I run `uname -r` as non-root + Then stdout matches regexp: + """ + intel + """ + When I run `pro enable realtime-kernel --variant generic` `with sudo` and stdin `y\ny\n` + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + realtime-kernel yes +enabled +Ubuntu kernel with PREEMPT_RT patches integrated + ├ generic yes +enabled +Generic version of the RT kernel \(default\) + └ intel-iotg yes +disabled +RT kernel optimized for Intel IOTG platform + """ + When I run `pro enable realtime-kernel --variant intel-iotg` `with sudo` and stdin `y\ny\n` + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + realtime-kernel yes +enabled +Ubuntu kernel with PREEMPT_RT patches integrated + ├ generic yes +disabled +Generic version of the RT kernel \(default\) + └ intel-iotg yes +enabled +RT kernel optimized for Intel IOTG platform + """ + When I verify that running `pro enable realtime-kernel` `with sudo` exits `1` + Then stdout contains substring: + """ + Real-time kernel is already enabled - nothing to do. + """ + When I run `pro disable realtime-kernel --assume-yes` with sudo + When I run `apt-cache policy ubuntu-intel-iot-realtime` as non-root + Then stdout contains substring: + """ + Installed: (none) + """ + # Test multiple variants + When I set the machine token overlay to the following yaml + """ + machineTokenInfo: + contractInfo: + resourceEntitlements: + - type: realtime-kernel + overrides: + - selector: + variant: nvidia-tegra + directives: + additionalPackages: + - nvidia-prime + - selector: + variant: raspi + directives: + additionalPackages: + - raspi-config + """ + When I run `pro enable realtime-kernel --variant nvidia-tegra` `with sudo` and stdin `y` + Then stdout matches regexp: + """ + One moment, checking your subscription first + The Real-time kernel is an Ubuntu kernel with PREEMPT_RT patches integrated. + + .*This will change your kernel. To revert to your original kernel, you will need + to make the change manually..* + + Do you want to continue\? \[ default = Yes \]: \(Y/n\) Configuring APT access to Real-time NVIDIA Tegra Kernel + Updating Real-time NVIDIA Tegra Kernel package lists + Updating standard Ubuntu package lists + Installing Real-time NVIDIA Tegra Kernel packages + Real-time NVIDIA Tegra Kernel enabled + """ + When I run `pro status` as non-root + Then stdout matches regexp: + """ + realtime-kernel\* yes +enabled +Ubuntu kernel with PREEMPT_RT patches integrated + usg +yes +disabled +Security compliance and audit tools + + \* Service has variants + """ + Then stdout contains substring: + """ + For a list of all Ubuntu Pro services and variants, run 'pro status --all' + """ + When I run `pro status --all` as non-root + Then stdout matches regexp: + """ + realtime-kernel yes +enabled +Ubuntu kernel with PREEMPT_RT patches integrated + ├ generic yes +disabled +Generic version of the RT kernel \(default\) + ├ intel-iotg yes +disabled +RT kernel optimized for Intel IOTG platform + ├ nvidia-tegra yes +enabled +RT kernel optimized for NVIDIA Tegra platform + └ raspi yes +disabled +24.04 Real-time kernel optimised for Raspberry Pi + """ + When I verify that running `pro enable realtime-kernel --variant intel-iotg` `with sudo` and stdin `N` exits `1` + Then stdout matches regexp: + """ + Real-time Intel IOTG Kernel cannot be enabled with Real-time NVIDIA Tegra Kernel. + Disable Real-time NVIDIA Tegra Kernel and proceed to enable Real-time Intel IOTG Kernel\? \(y/N\) + """ + And stdout matches regexp: + """ + Cannot enable Real-time Intel IOTG Kernel when Real-time NVIDIA Tegra Kernel is enabled. + """ + When I run `pro enable realtime-kernel --variant raspi --assume-yes` with sudo + When I run `pro status --all` as non-root + Then stdout matches regexp: + """ + realtime-kernel yes +enabled +Ubuntu kernel with PREEMPT_RT patches integrated + ├ generic yes +disabled +Generic version of the RT kernel \(default\) + ├ intel-iotg yes +disabled +RT kernel optimized for Intel IOTG platform + ├ nvidia-tegra yes +disabled +RT kernel optimized for NVIDIA Tegra platform + └ raspi yes +enabled +24.04 Real-time kernel optimised for Raspberry Pi + """ + When I run `pro help realtime-kernel` as non-root + Then I will see the following on stdout: + """ + Name: + realtime-kernel + + Entitled: + yes + + Status: + enabled + + Help: + The Real-time kernel is an Ubuntu kernel with PREEMPT_RT patches integrated. It + services latency-dependent use cases by providing deterministic response times. + The Real-time kernel meets stringent preemption specifications and is suitable + for telco applications and dedicated devices in industrial automation and + robotics. The Real-time kernel is currently incompatible with FIPS and + Livepatch. + + Variants: + + * generic: Generic version of the RT kernel (default) + * intel-iotg: RT kernel optimized for Intel IOTG platform + * nvidia-tegra: RT kernel optimized for NVIDIA Tegra platform + * raspi: 24.04 Real-time kernel optimised for Raspberry Pi + """ + When I run `pro disable realtime-kernel` `with sudo` and stdin `y` + Then stdout matches regexp: + """ + This will remove the boot order preference for the Real-time kernel and + disable updates to the Real-time kernel. + + This will NOT fully remove the kernel from your system. + + After this operation is complete you must: + - Ensure a different kernel is installed and configured to boot + - Reboot into that kernel + - Fully remove the realtime kernel packages from your system + - This might look something like `apt remove linux\*realtime`, + but you must ensure this is correct before running it. + """ + When I run `pro status` as non-root + Then stdout matches regexp: + """ + realtime-kernel\* +yes +disabled +Ubuntu kernel with PREEMPT_RT patches integrated + """ + When I run `pro status --all` as non-root + Then stdout matches regexp: + """ + realtime-kernel yes +disabled +Ubuntu kernel with PREEMPT_RT patches integrated + ├ generic yes +disabled +Generic version of the RT kernel \(default\) + ├ intel-iotg yes +disabled +RT kernel optimized for Intel IOTG platform + ├ nvidia-tegra yes +disabled +RT kernel optimized for NVIDIA Tegra platform + └ raspi yes +disabled +24.04 Real-time kernel optimised for Raspberry Pi + """ + When I verify that running `pro enable realtime-kernel --variant nonexistent` `with sudo` exits `1` + Then I will see the following on stderr: + """ + could not find entitlement named "nonexistent" + """ + When I run `pro detach --assume-yes` with sudo + And I run `pro status` as non-root + Then stdout matches regexp: + """ + realtime-kernel +yes +Ubuntu kernel with PREEMPT_RT patches integrated + """ + When I run `pro status --all` as non-root + Then stdout matches regexp: + """ + realtime-kernel +yes +Ubuntu kernel with PREEMPT_RT patches integrated + """ + And stdout does not match regexp: + """ + nvidia-tegra + """ + And stdout does not match regexp: + """ + intel-iotg + """ + + Examples: ubuntu release + | release | machine_type | + | jammy | lxd-vm | + + Scenario Outline: Enable Real-time kernel service access-only + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo and options `--no-auto-enable` + When I run `pro enable realtime-kernel --access-only` with sudo + Then stdout matches regexp: + """ + One moment, checking your subscription first + Configuring APT access to Real-time kernel + Updating Real-time kernel package lists + Skipping installing packages: ubuntu-realtime + Real-time kernel access enabled + """ + Then stdout does not match regexp: + """ + A reboot is required to complete install. + """ + When I run `apt-cache policy ubuntu-realtime` as non-root + Then stdout matches regexp: + """ + .*Installed: \(none\) + """ + And stdout matches regexp: + """ + \s* 500 https://esm.ubuntu.com/realtime/ubuntu /main amd64 Packages + """ + When I apt install `ubuntu-realtime` + When I reboot the machine + When I run `uname -r` as non-root + Then stdout matches regexp: + """ + realtime + """ + + Examples: ubuntu release + | release | machine_type | + | jammy | lxd-vm | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/reboot_cmds.feature ubuntu-advantage-tools-32~16.04/features/reboot_cmds.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/reboot_cmds.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/reboot_cmds.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,42 +1,43 @@ @uses.config.contract_token Feature: Reboot Commands - Scenario Outline: reboot-cmds removes fips package holds and updates packages - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - When I apt install `strongswan` - When I run `pro enable fips --assume-yes` with sudo - When I reboot the machine - Then I verify that `fips` is enabled - When I apt install `strongswan=` - When I run `apt-mark hold strongswan` with sudo - When I run `dpkg-reconfigure ubuntu-advantage-tools` with sudo - When I run `pro status` with sudo - Then stdout matches regexp: - """ - NOTICES - Reboot to FIPS kernel required - """ - When I reboot the machine - And I verify that running `systemctl status ua-reboot-cmds.service` `as non-root` exits `0,3` - Then stdout matches regexp: - """ - .*status=0\/SUCCESS.* - """ - When I run `pro status` with sudo - Then stdout does not match regexp: - """ - NOTICES - """ - When I run `apt-mark showholds` with sudo - Then I will see the following on stdout: - """ - """ - When I run `apt policy strongswan` with sudo - Then stdout contains substring: - """ - *** 1001 - """ - Examples: ubuntu release - | release | machine_type | old_version | new_version | - | focal | lxd-container | 5.8.2-1ubuntu3 | 5.8.2-1ubuntu3.fips.3.1.2 | + Scenario Outline: reboot-cmds removes fips package holds and updates packages + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + When I apt install `strongswan` + When I run `pro enable fips --assume-yes` with sudo + When I reboot the machine + Then I verify that `fips` is enabled + When I apt install `strongswan=` + When I run `apt-mark hold strongswan` with sudo + When I run `dpkg-reconfigure ubuntu-advantage-tools` with sudo + When I run `pro status` with sudo + Then stdout matches regexp: + """ + NOTICES + Reboot to FIPS kernel required + """ + When I reboot the machine + And I verify that running `systemctl status ua-reboot-cmds.service` `as non-root` exits `0,3` + Then stdout matches regexp: + """ + .*status=0\/SUCCESS.* + """ + When I run `pro status` with sudo + Then stdout does not match regexp: + """ + NOTICES + """ + When I run `apt-mark showholds` with sudo + Then I will see the following on stdout: + """ + """ + When I run `apt policy strongswan` with sudo + Then stdout contains substring: + """ + *** 1001 + """ + + Examples: ubuntu release + | release | machine_type | old_version | new_version | + | focal | lxd-container | 5.8.2-1ubuntu3 | 5.8.2-1ubuntu3.fips.3.1.2 | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/retry_auto_attach.feature ubuntu-advantage-tools-32~16.04/features/retry_auto_attach.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/retry_auto_attach.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/retry_auto_attach.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,378 +1,385 @@ Feature: auto-attach retries periodically on failures - Scenario Outline: auto-attach retries for a month and updates status - Given a `` `` machine with ubuntu-advantage-tools installed - When I change contract to staging with sudo - When I install ubuntu-advantage-pro - When I reboot the machine - When I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `3` - Then stdout matches regexp: - """ - Active: failed - """ - Then stdout matches regexp: - """ - creating flag file to trigger retries - """ - When I wait `20` seconds - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `0` - Then stdout matches regexp: - """ - Active: active \(running\) - """ - Then stdout matches regexp: - """ - mode: retry auto attach - """ - Then stdout does not match regexp: - """ - mode: poll for pro license - """ - When I run `run-parts /etc/update-motd.d/` with sudo - Then stdout matches regexp: - """ - Failed to automatically attach to an Ubuntu Pro subscription 1 time\(s\). - The failure was due to: Canonical servers did not recognize this machine as Ubuntu Pro: ".*". - The next attempt is scheduled for \d+-\d+-\d+T\d+:\d+:00.*. - You can try manually with `sudo pro auto-attach`. - """ - When I run `pro status` with sudo - Then stdout matches regexp: - """ - NOTICES - Failed to automatically attach to an Ubuntu Pro subscription 1 time\(s\). - The failure was due to: Canonical servers did not recognize this machine as Ubuntu Pro: ".*". - The next attempt is scheduled for \d+-\d+-\d+T\d+:\d+:00.*. - You can try manually with `sudo pro auto-attach`. - """ - - # simulate a middle attempt with different reason - When I set `interval_index` = `10` in json file `/var/lib/ubuntu-advantage/retry-auto-attach-state.json` - When I set `failure_reason` = `"an unknown error"` in json file `/var/lib/ubuntu-advantage/retry-auto-attach-state.json` - When I run `systemctl restart ubuntu-advantage.service` with sudo - And I wait `5` seconds - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `0` - Then stdout matches regexp: - """ - Active: active \(running\) - """ - Then stdout matches regexp: - """ - mode: retry auto attach - """ - Then stdout does not match regexp: - """ - mode: poll for pro license - """ - When I run `run-parts /etc/update-motd.d/` with sudo - Then stdout matches regexp: - """ - Failed to automatically attach to an Ubuntu Pro subscription 11 time\(s\). - The failure was due to: an unknown error. - The next attempt is scheduled for \d+-\d+-\d+T\d+:\d+:00.*. - You can try manually with `sudo pro auto-attach`. - """ - When I run `pro status` with sudo - Then stdout matches regexp: - """ - NOTICES - Failed to automatically attach to an Ubuntu Pro subscription 11 time\(s\). - The failure was due to: an unknown error. - The next attempt is scheduled for \d+-\d+-\d+T\d+:\d+:00.*. - You can try manually with `sudo pro auto-attach`. - """ - - # simulate all attempts failing - When I set `interval_index` = `18` in json file `/var/lib/ubuntu-advantage/retry-auto-attach-state.json` - When I run `systemctl restart ubuntu-advantage.service` with sudo - And I wait `5` seconds - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` - Then stdout contains substring - """ - Active: inactive (dead) - """ - Then stdout matches regexp: - """ - mode: retry auto attach - """ - Then stdout does not match regexp: - """ - mode: poll for pro license - """ - When I run `run-parts /etc/update-motd.d/` with sudo - Then stdout matches regexp: - """ - Failed to automatically attach to an Ubuntu Pro subscription 19 time\(s\). - The most recent failure was due to: an unknown error. - Try re-launching the instance or report this issue by running `ubuntu-bug ubuntu-advantage-tools` - You can try manually with `sudo pro auto-attach`. - """ - When I run `pro status` with sudo - Then stdout matches regexp: - """ - NOTICES - Failed to automatically attach to an Ubuntu Pro subscription 19 time\(s\). - The most recent failure was due to: an unknown error. - Try re-launching the instance or report this issue by running `ubuntu-bug ubuntu-advantage-tools` - You can try manually with `sudo pro auto-attach`. - """ - Examples: ubuntu release - | release | machine_type | - | xenial | aws.generic | - | xenial | azure.generic | - | xenial | gcp.generic | - | bionic | aws.generic | - | bionic | azure.generic | - | bionic | gcp.generic | - | focal | aws.generic | - | focal | azure.generic | - | focal | gcp.generic | - | jammy | aws.generic | - | jammy | azure.generic | - | jammy | gcp.generic | - - - Scenario Outline: auto-attach retries stop if manual auto-attach succeeds - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - data_dir: /var/lib/ubuntu-advantage - log_level: debug - log_file: /var/log/ubuntu-advantage.log - - """ - When I create the file `/var/lib/ubuntu-advantage/response-overlay.json` with the following: - """ - { - "https://contracts.canonical.com/v1/clouds/$behave_var{cloud system-under-test}/token": [{ - "type": "contract", - "code": 400, - "response": { - "message": "error" - } - }] - } - """ - And I append the following on uaclient config: - """ - features: - serviceclient_url_responses: "/var/lib/ubuntu-advantage/response-overlay.json" - """ - When I reboot the machine - When I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `3` - Then stdout matches regexp: - """ - Active: failed - """ - Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `0` - Then stdout matches regexp: - """ - Active: active \(running\) - """ - When I wait `20` seconds - When I run `run-parts /etc/update-motd.d/` with sudo - Then stdout matches regexp: - """ - Failed to automatically attach to an Ubuntu Pro subscription - """ - When I run `pro status` with sudo - Then stdout matches regexp: - """ - NOTICES - Failed to automatically attach to an Ubuntu Pro subscription - """ - When I append the following on uaclient config: - """ - features: {} - """ - # The retry service waits 15 minutes before trying again, so this - # _should_ run and finish before the retry service has done anything - When I run `pro auto-attach` with sudo - When I verify that running `systemctl status ubuntu-advantage.service` `as non-root` exits `3` - Then stdout contains substring - """ - Active: inactive (dead) - """ - # Workaround for livepatch issue LP #2015585 - Then I verify that running `run-parts /etc/update-motd.d/` `with sudo` exits `0,1` - Then stdout does not match regexp: - """ - Failed to automatically attach to an Ubuntu Pro subscription - """ - When I run `pro status` with sudo - Then stdout does not match regexp: - """ - NOTICES - Failed to automatically attach to an Ubuntu Pro subscription - """ - Examples: ubuntu release - | release | machine_type | - | xenial | aws.pro | - | xenial | azure.pro | - | xenial | gcp.pro | - | bionic | aws.pro | - | bionic | azure.pro | - | bionic | gcp.pro | - | focal | aws.pro | - | focal | azure.pro | - | focal | gcp.pro | - | jammy | aws.pro | - | jammy | azure.pro | - | jammy | gcp.pro | - - Scenario Outline: gcp auto-detect triggers retries on fail - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - data_dir: /var/lib/ubuntu-advantage - log_level: debug - log_file: /var/log/ubuntu-advantage.log - - """ - When I create the file `/var/lib/ubuntu-advantage/response-overlay.json` with the following: - """ - { - "https://contracts.canonical.com/v1/clouds/gcp/token": [{ - "type": "contract", - "code": 400, - "response": { - "message": "error" - } - }] - } - """ - And I append the following on uaclient config: - """ - features: - serviceclient_url_responses: "/var/lib/ubuntu-advantage/response-overlay.json" - """ - When I run `systemctl start ubuntu-advantage.service` with sudo - When I wait `1` seconds - When I verify that running `systemctl status ubuntu-advantage.service` `as non-root` exits `0` - Then stdout contains substring - """ - Active: active (running) - """ - Then stdout matches regexp: - """ - mode: poll for pro license - """ - Then stdout matches regexp: - """ - creating flag file to trigger retries - """ - Then stdout matches regexp: - """ - mode: retry auto attach - """ - When I run `run-parts /etc/update-motd.d/` with sudo - Then stdout matches regexp: - """ - Failed to automatically attach to an Ubuntu Pro subscription - """ - When I run `pro status` with sudo - Then stdout matches regexp: - """ - NOTICES - Failed to automatically attach to an Ubuntu Pro subscription - """ - Examples: ubuntu release - | release | machine_type | - | xenial | gcp.pro | - | bionic | gcp.pro | - | focal | gcp.pro | - | jammy | gcp.pro | - - - Scenario Outline: auto-attach retries eventually succeed and clean up - Given a `` `` machine with ubuntu-advantage-tools installed - # modify the wait time to be shorter so we don't have to wait 15m - When I replace `900, # 15m (T+15m)` in `/usr/lib/python3/dist-packages/uaclient/daemon/retry_auto_attach.py` with `60,` - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - data_dir: /var/lib/ubuntu-advantage - log_level: debug - log_file: /var/log/ubuntu-advantage.log - - """ - When I create the file `/var/lib/ubuntu-advantage/response-overlay.json` with the following: - """ - { - "https://contracts.canonical.com/v1/clouds/$behave_var{cloud system-under-test}/token": [{ - "type": "contract", - "code": 400, - "response": { - "message": "error" - } - }] - } - """ - And I append the following on uaclient config: - """ - features: - serviceclient_url_responses: "/var/lib/ubuntu-advantage/response-overlay.json" - """ - When I reboot the machine - When I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `3` - Then stdout matches regexp: - """ - Active: failed - """ - When I verify that running `systemctl status ubuntu-advantage.service` `as non-root` exits `0` - Then stdout matches regexp: - """ - Active: active \(running\) - """ - When I wait `20` seconds - When I run `run-parts /etc/update-motd.d/` with sudo - Then stdout matches regexp: - """ - Failed to automatically attach to an Ubuntu Pro subscription - """ - When I run `pro status` with sudo - Then stdout matches regexp: - """ - NOTICES - Failed to automatically attach to an Ubuntu Pro subscription - """ - When I append the following on uaclient config: - """ - features: {} - """ - When I wait `60` seconds - And I run `pro status --wait` with sudo - Then the machine is attached - When I verify that running `systemctl status ubuntu-advantage.service` `as non-root` exits `3` - Then stdout contains substring - """ - Active: inactive (dead) - """ - # Workaround for livepatch issue LP #2015585 - Then I verify that running `run-parts /etc/update-motd.d/` `with sudo` exits `0,1` - Then stdout does not match regexp: - """ - Failed to automatically attach to an Ubuntu Pro subscription - """ - When I run `pro status` with sudo - Then stdout does not match regexp: - """ - NOTICES - Failed to automatically attach to an Ubuntu Pro subscription - """ - Examples: ubuntu release - | release | machine_type | - | xenial | aws.pro | - | xenial | azure.pro | - | xenial | gcp.pro | - | bionic | aws.pro | - | bionic | azure.pro | - | bionic | gcp.pro | - | focal | aws.pro | - | focal | azure.pro | - | focal | gcp.pro | - | jammy | aws.pro | - | jammy | azure.pro | - | jammy | gcp.pro | + Scenario Outline: auto-attach retries for a month and updates status + Given a `` `` machine with ubuntu-advantage-tools installed + When I change contract to staging with sudo + When I install ubuntu-advantage-pro + When I reboot the machine + When I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `3` + Then stdout matches regexp: + """ + Active: failed + """ + Then stdout matches regexp: + """ + creating flag file to trigger retries + """ + When I wait `20` seconds + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `0` + Then stdout matches regexp: + """ + Active: active \(running\) + """ + Then stdout matches regexp: + """ + mode: retry auto attach + """ + Then stdout does not match regexp: + """ + mode: poll for pro license + """ + When I run `run-parts /etc/update-motd.d/` with sudo + Then stdout matches regexp: + """ + Failed to automatically attach to an Ubuntu Pro subscription 1 time\(s\). + The failure was due to: Canonical servers did not recognize this machine as Ubuntu Pro: ".*". + The next attempt is scheduled for \d+-\d+-\d+T\d+:\d+:00.*. + You can try manually with `sudo pro auto-attach`. + """ + When I run `pro status` with sudo + Then stdout matches regexp: + """ + NOTICES + Failed to automatically attach to an Ubuntu Pro subscription 1 time\(s\). + The failure was due to: Canonical servers did not recognize this machine as Ubuntu Pro: ".*". + The next attempt is scheduled for \d+-\d+-\d+T\d+:\d+:00.*. + You can try manually with `sudo pro auto-attach`. + """ + # simulate a middle attempt with different reason + When I set `interval_index` = `10` in json file `/var/lib/ubuntu-advantage/retry-auto-attach-state.json` + When I set `failure_reason` = `"an unknown error"` in json file `/var/lib/ubuntu-advantage/retry-auto-attach-state.json` + When I run `systemctl restart ubuntu-advantage.service` with sudo + And I wait `5` seconds + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `0` + Then stdout matches regexp: + """ + Active: active \(running\) + """ + Then stdout matches regexp: + """ + mode: retry auto attach + """ + Then stdout does not match regexp: + """ + mode: poll for pro license + """ + When I run `run-parts /etc/update-motd.d/` with sudo + Then stdout matches regexp: + """ + Failed to automatically attach to an Ubuntu Pro subscription 11 time\(s\). + The failure was due to: an unknown error. + The next attempt is scheduled for \d+-\d+-\d+T\d+:\d+:00.*. + You can try manually with `sudo pro auto-attach`. + """ + When I run `pro status` with sudo + Then stdout matches regexp: + """ + NOTICES + Failed to automatically attach to an Ubuntu Pro subscription 11 time\(s\). + The failure was due to: an unknown error. + The next attempt is scheduled for \d+-\d+-\d+T\d+:\d+:00.*. + You can try manually with `sudo pro auto-attach`. + """ + # simulate all attempts failing + When I set `interval_index` = `18` in json file `/var/lib/ubuntu-advantage/retry-auto-attach-state.json` + When I run `systemctl restart ubuntu-advantage.service` with sudo + And I wait `5` seconds + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `3` + Then stdout contains substring + """ + Active: inactive (dead) + """ + Then stdout matches regexp: + """ + mode: retry auto attach + """ + Then stdout does not match regexp: + """ + mode: poll for pro license + """ + When I run `run-parts /etc/update-motd.d/` with sudo + Then stdout matches regexp: + """ + Failed to automatically attach to an Ubuntu Pro subscription 19 time\(s\). + The most recent failure was due to: an unknown error. + Try re-launching the instance or report this issue by running `ubuntu-bug ubuntu-advantage-tools` + You can try manually with `sudo pro auto-attach`. + """ + When I run `pro status` with sudo + Then stdout matches regexp: + """ + NOTICES + Failed to automatically attach to an Ubuntu Pro subscription 19 time\(s\). + The most recent failure was due to: an unknown error. + Try re-launching the instance or report this issue by running `ubuntu-bug ubuntu-advantage-tools` + You can try manually with `sudo pro auto-attach`. + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | aws.generic | + | xenial | azure.generic | + | xenial | gcp.generic | + | bionic | aws.generic | + | bionic | azure.generic | + | bionic | gcp.generic | + | focal | aws.generic | + | focal | azure.generic | + | focal | gcp.generic | + | jammy | aws.generic | + | jammy | azure.generic | + | jammy | gcp.generic | + | noble | aws.generic | + | noble | azure.generic | + | noble | gcp.generic | + + Scenario Outline: auto-attach retries stop if manual auto-attach succeeds + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + data_dir: /var/lib/ubuntu-advantage + log_level: debug + log_file: /var/log/ubuntu-advantage.log + """ + When I create the file `/var/lib/ubuntu-advantage/response-overlay.json` with the following: + """ + { + "https://contracts.canonical.com/v1/clouds/$behave_var{cloud system-under-test}/token": [{ + "type": "contract", + "code": 400, + "response": { + "message": "error" + } + }] + } + """ + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + features: + serviceclient_url_responses: "/var/lib/ubuntu-advantage/response-overlay.json" + """ + When I reboot the machine + When I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `3` + Then stdout matches regexp: + """ + Active: failed + """ + Then I verify that running `systemctl status ubuntu-advantage.service` `with sudo` exits `0` + Then stdout matches regexp: + """ + Active: active \(running\) + """ + When I wait `20` seconds + When I run `run-parts /etc/update-motd.d/` with sudo + Then stdout matches regexp: + """ + Failed to automatically attach to an Ubuntu Pro subscription + """ + When I run `pro status` with sudo + Then stdout matches regexp: + """ + NOTICES + Failed to automatically attach to an Ubuntu Pro subscription + """ + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + features: {} + """ + # The retry service waits 15 minutes before trying again, so this + # _should_ run and finish before the retry service has done anything + When I run `pro auto-attach` with sudo + When I verify that running `systemctl status ubuntu-advantage.service` `as non-root` exits `3` + Then stdout contains substring + """ + Active: inactive (dead) + """ + # Workaround for livepatch issue LP #2015585 + Then I verify that running `run-parts /etc/update-motd.d/` `with sudo` exits `0,1` + Then stdout does not match regexp: + """ + Failed to automatically attach to an Ubuntu Pro subscription + """ + When I run `pro status` with sudo + Then stdout does not match regexp: + """ + NOTICES + Failed to automatically attach to an Ubuntu Pro subscription + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | aws.pro | + | xenial | azure.pro | + | xenial | gcp.pro | + | bionic | aws.pro | + | bionic | azure.pro | + | bionic | gcp.pro | + | focal | aws.pro | + | focal | azure.pro | + | focal | gcp.pro | + | jammy | aws.pro | + | jammy | azure.pro | + | jammy | gcp.pro | + | noble | aws.pro | + | noble | azure.pro | + | noble | gcp.pro | + + Scenario Outline: gcp auto-detect triggers retries on fail + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + data_dir: /var/lib/ubuntu-advantage + log_level: debug + log_file: /var/log/ubuntu-advantage.log + """ + When I create the file `/var/lib/ubuntu-advantage/response-overlay.json` with the following: + """ + { + "https://contracts.canonical.com/v1/clouds/gcp/token": [{ + "type": "contract", + "code": 400, + "response": { + "message": "error" + } + }] + } + """ + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + features: + serviceclient_url_responses: "/var/lib/ubuntu-advantage/response-overlay.json" + """ + When I run `systemctl start ubuntu-advantage.service` with sudo + When I wait `1` seconds + When I verify that running `systemctl status ubuntu-advantage.service` `as non-root` exits `0` + Then stdout contains substring + """ + Active: active (running) + """ + Then stdout matches regexp: + """ + mode: poll for pro license + """ + Then stdout matches regexp: + """ + creating flag file to trigger retries + """ + Then stdout matches regexp: + """ + mode: retry auto attach + """ + When I run `run-parts /etc/update-motd.d/` with sudo + Then stdout matches regexp: + """ + Failed to automatically attach to an Ubuntu Pro subscription + """ + When I run `pro status` with sudo + Then stdout matches regexp: + """ + NOTICES + Failed to automatically attach to an Ubuntu Pro subscription + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | gcp.pro | + | bionic | gcp.pro | + | focal | gcp.pro | + | jammy | gcp.pro | + | noble | gcp.pro | + + Scenario Outline: auto-attach retries eventually succeed and clean up + Given a `` `` machine with ubuntu-advantage-tools installed + # modify the wait time to be shorter so we don't have to wait 15m + When I replace `900, # 15m (T+15m)` in `/usr/lib/python3/dist-packages/uaclient/daemon/retry_auto_attach.py` with `60,` + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + data_dir: /var/lib/ubuntu-advantage + log_level: debug + log_file: /var/log/ubuntu-advantage.log + """ + When I create the file `/var/lib/ubuntu-advantage/response-overlay.json` with the following: + """ + { + "https://contracts.canonical.com/v1/clouds/$behave_var{cloud system-under-test}/token": [{ + "type": "contract", + "code": 400, + "response": { + "message": "error" + } + }] + } + """ + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + features: + serviceclient_url_responses: "/var/lib/ubuntu-advantage/response-overlay.json" + """ + When I reboot the machine + When I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `3` + Then stdout matches regexp: + """ + Active: failed + """ + When I verify that running `systemctl status ubuntu-advantage.service` `as non-root` exits `0` + Then stdout matches regexp: + """ + Active: active \(running\) + """ + When I wait `20` seconds + When I run `run-parts /etc/update-motd.d/` with sudo + Then stdout matches regexp: + """ + Failed to automatically attach to an Ubuntu Pro subscription + """ + When I run `pro status` with sudo + Then stdout matches regexp: + """ + NOTICES + Failed to automatically attach to an Ubuntu Pro subscription + """ + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + features: {} + """ + When I wait `60` seconds + And I run `pro status --wait` with sudo + Then the machine is attached + When I verify that running `systemctl status ubuntu-advantage.service` `as non-root` exits `3` + Then stdout contains substring + """ + Active: inactive (dead) + """ + # Workaround for livepatch issue LP #2015585 + Then I verify that running `run-parts /etc/update-motd.d/` `with sudo` exits `0,1` + Then stdout does not match regexp: + """ + Failed to automatically attach to an Ubuntu Pro subscription + """ + When I run `pro status` with sudo + Then stdout does not match regexp: + """ + NOTICES + Failed to automatically attach to an Ubuntu Pro subscription + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | aws.pro | + | xenial | azure.pro | + | xenial | gcp.pro | + | bionic | aws.pro | + | bionic | azure.pro | + | bionic | gcp.pro | + | focal | aws.pro | + | focal | azure.pro | + | focal | gcp.pro | + | jammy | aws.pro | + | jammy | azure.pro | + | jammy | gcp.pro | + | noble | aws.pro | + | noble | azure.pro | + | noble | gcp.pro | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/schemas/attach.json ubuntu-advantage-tools-32~16.04/features/schemas/attach.json --- ubuntu-advantage-tools-31.2.3~16.04/features/schemas/attach.json 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/schemas/attach.json 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,15 @@ +{ + "type": "object", + "required": [ "enabled", "reboot_required"], + "properties": { + "enabled": { + "type": "array", + "items": { + "type": "string" + } + }, + "reboot_required": { + "type": "boolean" + } + } +} diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/schemas/detach.json ubuntu-advantage-tools-32~16.04/features/schemas/detach.json --- ubuntu-advantage-tools-31.2.3~16.04/features/schemas/detach.json 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/schemas/detach.json 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,15 @@ +{ + "type": "object", + "required": [ "disabled", "reboot_required"], + "properties": { + "disabled": { + "type": "array", + "items": { + "type": "string" + } + }, + "reboot_required": { + "type": "boolean" + } + } +} diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/security_status.feature ubuntu-advantage-tools-32~16.04/features/security_status.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/security_status.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/security_status.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,854 +1,860 @@ Feature: Security status command behavior - @uses.config.contract_token - Scenario Outline: Run security status with JSON/YAML format - Given a `` `` machine with ubuntu-advantage-tools installed - When I apt update - And I apt install `ansible` - And I run `pro security-status --format json` as non-root - Then stdout is a json matching the `ua_security_status` schema - And stdout matches regexp: - """ - "_schema_version": "0.1" - """ - And stdout matches regexp: - """ - "attached": false - """ - And stdout matches regexp: - """ - "enabled_services": \[\] - """ - And stdout matches regexp: - """ - "entitled_services": \[\] - """ - And stdout matches regexp: - """ - "package": "" - """ - And stdout matches regexp: - """ - "service_name": "" - """ - And stdout matches regexp: - """ - "origin": "esm.ubuntu.com" - """ - And stdout matches regexp: - """ - "status": "pending_attach" - """ - And stdout matches regexp: - """ - "download_size": \d+ - """ - When I attach `contract_token` with sudo - And I run `pro security-status --format json` as non-root - Then stdout is a json matching the `ua_security_status` schema - Then stdout matches regexp: - """ - "_schema_version": "0.1" - """ - And stdout matches regexp: - """ - "attached": true - """ - And stdout matches regexp: - """ - "enabled_services": \["esm-apps", "esm-infra"\] - """ - And stdout matches regexp: - """ - "entitled_services": \["esm-apps", "esm-infra"\] - """ - And stdout matches regexp: - """ - "status": "upgrade_available" - """ - And stdout matches regexp: - """ - "download_size": \d+ - """ - When I run `pro security-status --format yaml` as non-root - Then stdout is a yaml matching the `ua_security_status` schema - And stdout matches regexp: - """ - _schema_version: '0.1' - """ - When I verify that running `pro security-status --format unsupported` `as non-root` exits `2` - Then I will see the following on stderr: - """ - usage: security-status [-h] [--format {json,yaml,text}] - [--thirdparty | --unavailable | --esm-infra | --esm-apps] - argument --format: invalid choice: 'unsupported' (choose from 'json', 'yaml', 'text') - """ - Examples: ubuntu release - | release | machine_type | package | service | - | xenial | lxd-container | apport | esm-infra | - | bionic | lxd-container | ansible | esm-apps | - - @uses.config.contract_token - Scenario: Check for livepatch CVEs in security-status on an Ubuntu machine - Given a `xenial` `lxd-vm` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `pro security-status --format json` as non-root - Then stdout is a json matching the `ua_security_status` schema - Then stdout matches regexp: - """ - {"name": "cve-2013-1798", "patched": true} - """ - When I run `pro security-status --format yaml` as non-root - Then stdout is a yaml matching the `ua_security_status` schema - And stdout matches regexp: - """ - - name: cve-2013-1798 - patched: true - """ - - @uses.config.contract_token - Scenario: Run security status in an Ubuntu machine - Given a `xenial` `lxd-container` machine with ubuntu-advantage-tools installed - When I install third-party / unknown packages in the machine - # Ansible is in esm-apps - And I apt install `ansible` - And I verify root and non-root `pro security-status` calls have the same output - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - This machine is NOT receiving security patches because the LTS period has ended - and esm-infra is not enabled. - This machine is NOT attached to an Ubuntu Pro subscription. - - Ubuntu Pro with 'esm-infra' enabled provides security updates for - Main/Restricted packages until 2026\. There (is|are) \d+ pending security update[s]?\. - - Ubuntu Pro with 'esm-apps' enabled provides security updates for - Universe/Multiverse packages until 2026\. There (is|are) \d+ pending security update[s]?\. - - Try Ubuntu Pro with a free personal subscription on up to 5 machines. - Learn more at https://ubuntu.com/pro - """ - When I verify root and non-root `pro security-status --esm-infra` calls have the same output - And I run `pro security-status --esm-infra` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Main/Restricted repository - - This machine is NOT receiving security patches because the LTS period has ended - and esm-infra is not enabled. - - Ubuntu Pro with 'esm-infra' enabled provides security updates for - Main/Restricted packages until 2026\. There (is|are) \d+ pending security update[s]?\. - - Run 'pro help esm-infra' to learn more - - Installed packages with an available esm-infra update: - (.|\n)+ - - Further installed packages covered by esm-infra: - (.|\n)+ - - For example, run: - apt-cache show .+ - to learn more about that package\. - """ - When I verify root and non-root `pro security-status --esm-apps` calls have the same output - And I run `pro security-status --esm-apps` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - - Ubuntu Pro with 'esm-apps' enabled provides security updates for - Universe/Multiverse packages until 2026\. There (is|are) \d+ pending security update[s]?\. - - Run 'pro help esm-apps' to learn more - - Installed packages with an available esm-apps update: - (.|\n)+ - - Further installed packages covered by esm-apps: - (.|\n)+ - - For example, run: - apt-cache show .+ - to learn more about that package\. - """ - When I attach `contract_token` with sudo - And I verify root and non-root `pro security-status` calls have the same output - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - This machine is attached to an Ubuntu Pro subscription. - - Main/Restricted packages are receiving security updates from - Ubuntu Pro with 'esm-infra' enabled until 2026\. There (is|are) \d+ pending security update[s]?\. - - Universe/Multiverse packages are receiving security updates from - Ubuntu Pro with 'esm-apps' enabled until 2026\. There (is|are) \d+ pending security update[s]?\. - """ - When I verify root and non-root `pro security-status --esm-infra` calls have the same output - And I run `pro security-status --esm-infra` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ packages from Ubuntu Main/Restricted repository - - Main/Restricted packages are receiving security updates from - Ubuntu Pro with 'esm-infra' enabled until 2026\. There (is|are) \d+ pending security update[s]?\. - - Run 'pro help esm-infra' to learn more - - Installed packages with an available esm-infra update: - (.|\n)+ - - Further installed packages covered by esm-infra: - (.|\n)+ - - For example, run: - apt-cache show .+ - to learn more about that package\. - """ - When I verify root and non-root `pro security-status --esm-apps` calls have the same output - And I run `pro security-status --esm-apps` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - - Universe/Multiverse packages are receiving security updates from - Ubuntu Pro with 'esm-apps' enabled until 2026\. There (is|are) \d+ pending security update[s]?\. - - Run 'pro help esm-apps' to learn more - - Installed packages with an available esm-apps update: - (.|\n)+ - - Further installed packages covered by esm-apps: - (.|\n)+ - - For example, run: - apt-cache show .+ - to learn more about that package\. - """ - When I apt upgrade - And I verify root and non-root `pro security-status` calls have the same output - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - This machine is attached to an Ubuntu Pro subscription. - - Main/Restricted packages are receiving security updates from - Ubuntu Pro with 'esm-infra' enabled until 2026\. You have received \d+ security - update[s]?\. - - Universe/Multiverse packages are receiving security updates from - Ubuntu Pro with 'esm-apps' enabled until 2026\. You have received \d+ security - update[s]?\. - """ - When I run `pro disable esm-infra esm-apps` with sudo - And I verify root and non-root `pro security-status` calls have the same output - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - This machine is NOT receiving security patches because the LTS period has ended - and esm-infra is not enabled. - This machine is attached to an Ubuntu Pro subscription. - - Ubuntu Pro with 'esm-infra' enabled provides security updates for - Main/Restricted packages until 2026. - - Enable esm-infra with: pro enable esm-infra - - Ubuntu Pro with 'esm-apps' enabled provides security updates for - Universe/Multiverse packages until 2026. - - Enable esm-apps with: pro enable esm-apps - """ - When I verify root and non-root `pro security-status --thirdparty` calls have the same output - And I run `pro security-status --thirdparty` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +1 package from a third party - - Packages from third parties are not provided by the official Ubuntu - archive, for example packages from Personal Package Archives in Launchpad\. - - Packages: - (.|\n)+ - - For example, run: - apt-cache show .+ - to learn more about that package\. - """ - When I verify root and non-root `pro security-status --unavailable` calls have the same output - And I run `pro security-status --unavailable` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? no longer available for download - - Packages that are not available for download may be left over from a - previous release of Ubuntu, may have been installed directly from a - .deb file, or are from a source which has been disabled\. - - Packages: - (.|\n)+ - - For example, run: - apt-cache show .+ - to learn more about that package\. - """ - When I verify root and non-root `pro security-status --esm-infra` calls have the same output - And I run `pro security-status --esm-infra` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ packages from Ubuntu Main/Restricted repository - - This machine is NOT receiving security patches because the LTS period has ended - and esm-infra is not enabled. - - Ubuntu Pro with 'esm-infra' enabled provides security updates for - Main/Restricted packages until 2026. - - Run 'pro help esm-infra' to learn more - - Installed packages covered by esm-infra: - (.|\n)+ - """ - When I verify root and non-root `pro security-status --esm-apps` calls have the same output - And I run `pro security-status --esm-apps` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - - Ubuntu Pro with 'esm-apps' enabled provides security updates for - Universe/Multiverse packages until 2026. - - Run 'pro help esm-apps' to learn more - - Installed packages covered by esm-apps: - (.|\n)+ - """ - When I verify that running `pro security-status --thirdparty --unavailable` `as non-root` exits `2` - Then I will see the following on stderr - """ - usage: security-status [-h] [--format {json,yaml,text}] - [--thirdparty | --unavailable | --esm-infra | --esm-apps] - argument --unavailable: not allowed with argument --thirdparty - """ - When I run `rm /var/lib/apt/periodic/update-success-stamp` with sudo - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - The system apt cache may be outdated\. Make sure to run - sudo apt update - to get the latest package information from apt\. - - This machine is NOT receiving security patches because the LTS period has ended - and esm-infra is not enabled. - This machine is attached to an Ubuntu Pro subscription. - - Ubuntu Pro with 'esm-infra' enabled provides security updates for - Main/Restricted packages until 2026. - - Enable esm-infra with: pro enable esm-infra - - Ubuntu Pro with 'esm-apps' enabled provides security updates for - Universe/Multiverse packages until 2026. - - Enable esm-apps with: pro enable esm-apps - """ - When I run `touch -d '-2 days' /var/lib/apt/periodic/update-success-stamp` with sudo - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - The system apt information was updated 2 day\(s\) ago\. Make sure to run - sudo apt update - to get the latest package information from apt\. - - This machine is NOT receiving security patches because the LTS period has ended - and esm-infra is not enabled. - This machine is attached to an Ubuntu Pro subscription. - - Ubuntu Pro with 'esm-infra' enabled provides security updates for - Main/Restricted packages until 2026. - - Enable esm-infra with: pro enable esm-infra - - Ubuntu Pro with 'esm-apps' enabled provides security updates for - Universe/Multiverse packages until 2026. - - Enable esm-apps with: pro enable esm-apps - """ - - @uses.config.contract_token - Scenario: Run security status in an Ubuntu machine - Given a `focal` `lxd-container` machine with ubuntu-advantage-tools installed - When I install third-party / unknown packages in the machine - # Ansible is in esm-apps - And I apt install `ansible` - And I verify root and non-root `pro security-status` calls have the same output - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - This machine is receiving security patching for Ubuntu Main/Restricted - repository until 2025. - This machine is NOT attached to an Ubuntu Pro subscription. - - Ubuntu Pro with 'esm-infra' enabled provides security updates for - Main/Restricted packages until 2030. - - Ubuntu Pro with 'esm-apps' enabled provides security updates for - Universe/Multiverse packages until 2030\. There (is|are) \d+ pending security update[s]?\. - - Try Ubuntu Pro with a free personal subscription on up to 5 machines. - Learn more at https://ubuntu.com/pro - """ - When I verify root and non-root `pro security-status --esm-infra` calls have the same output - And I run `pro security-status --esm-infra` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Main/Restricted repository - - This machine is receiving security patching for Ubuntu Main/Restricted - repository until 2025. - - Ubuntu Pro with 'esm-infra' enabled provides security updates for - Main/Restricted packages until 2030. - - Run 'pro help esm-infra' to learn more - """ - When I verify root and non-root `pro security-status --esm-apps` calls have the same output - And I run `pro security-status --esm-apps` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - - Ubuntu Pro with 'esm-apps' enabled provides security updates for - Universe/Multiverse packages until 2030\. There (is|are) \d+ pending security update[s]?\. - - Run 'pro help esm-apps' to learn more - - Installed packages with an available esm-apps update: - (.|\n)+ - - Further installed packages covered by esm-apps: - (.|\n)+ - - For example, run: - apt-cache show .+ - to learn more about that package\. - """ - When I attach `contract_token` with sudo - And I verify root and non-root `pro security-status` calls have the same output - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - This machine is attached to an Ubuntu Pro subscription. - - Main/Restricted packages are receiving security updates from - Ubuntu Pro with 'esm-infra' enabled until 2030. - - Universe/Multiverse packages are receiving security updates from - Ubuntu Pro with 'esm-apps' enabled until 2030\. There (is|are) \d+ pending security update[s]?\. - """ - When I verify root and non-root `pro security-status --esm-infra` calls have the same output - And I run `pro security-status --esm-infra` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ packages from Ubuntu Main/Restricted repository - - Main/Restricted packages are receiving security updates from - Ubuntu Pro with 'esm-infra' enabled until 2030. - - Run 'pro help esm-infra' to learn more - """ - When I verify root and non-root `pro security-status --esm-apps` calls have the same output - And I run `pro security-status --esm-apps` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - - Universe/Multiverse packages are receiving security updates from - Ubuntu Pro with 'esm-apps' enabled until 2030\. There (is|are) \d+ pending security update[s]?\. - - Run 'pro help esm-apps' to learn more - - Installed packages with an available esm-apps update: - (.|\n)+ - - Further installed packages covered by esm-apps: - (.|\n)+ - - For example, run: - apt-cache show .+ - to learn more about that package\. - """ - When I apt upgrade - And I verify root and non-root `pro security-status` calls have the same output - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - This machine is attached to an Ubuntu Pro subscription. - - Main/Restricted packages are receiving security updates from - Ubuntu Pro with 'esm-infra' enabled until 2030\. - - Universe/Multiverse packages are receiving security updates from - Ubuntu Pro with 'esm-apps' enabled until 2030\. You have received \d+ security - update[s]?\. - """ - When I run `pro disable esm-infra esm-apps` with sudo - And I verify root and non-root `pro security-status` calls have the same output - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - This machine is receiving security patching for Ubuntu Main/Restricted - repository until 2025. - This machine is attached to an Ubuntu Pro subscription. - - Ubuntu Pro with 'esm-infra' enabled provides security updates for - Main/Restricted packages until 2030. - - Enable esm-infra with: pro enable esm-infra - - Ubuntu Pro with 'esm-apps' enabled provides security updates for - Universe/Multiverse packages until 2030. - - Enable esm-apps with: pro enable esm-apps - """ - When I verify root and non-root `pro security-status --thirdparty` calls have the same output - And I run `pro security-status --thirdparty` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +1 package from a third party - - Packages from third parties are not provided by the official Ubuntu - archive, for example packages from Personal Package Archives in Launchpad\. - - Packages: - (.|\n)+ - - For example, run: - apt-cache show .+ - to learn more about that package\. - """ - When I verify root and non-root `pro security-status --unavailable` calls have the same output - And I run `pro security-status --unavailable` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? no longer available for download - - Packages that are not available for download may be left over from a - previous release of Ubuntu, may have been installed directly from a - .deb file, or are from a source which has been disabled\. - - Packages: - (.|\n)+ - - For example, run: - apt-cache show .+ - to learn more about that package\. - """ - When I verify root and non-root `pro security-status --esm-infra` calls have the same output - And I run `pro security-status --esm-infra` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ packages from Ubuntu Main/Restricted repository - - This machine is receiving security patching for Ubuntu Main/Restricted - repository until 2025. - - Ubuntu Pro with 'esm-infra' enabled provides security updates for - Main/Restricted packages until 2030. - - Run 'pro help esm-infra' to learn more - """ - When I verify root and non-root `pro security-status --esm-apps` calls have the same output - And I run `pro security-status --esm-apps` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - - Ubuntu Pro with 'esm-apps' enabled provides security updates for - Universe/Multiverse packages until 2030. - - Run 'pro help esm-apps' to learn more - - Installed packages covered by esm-apps: - (.|\n)+ - """ - When I verify that running `pro security-status --thirdparty --unavailable` `as non-root` exits `2` - Then I will see the following on stderr - """ - usage: security-status [-h] [--format {json,yaml,text}] - [--thirdparty | --unavailable | --esm-infra | --esm-apps] - argument --unavailable: not allowed with argument --thirdparty - """ - When I run `rm /var/lib/apt/periodic/update-success-stamp` with sudo - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - The system apt cache may be outdated\. Make sure to run - sudo apt update - to get the latest package information from apt\. - - This machine is receiving security patching for Ubuntu Main/Restricted - repository until 2025. - This machine is attached to an Ubuntu Pro subscription. - - Ubuntu Pro with 'esm-infra' enabled provides security updates for - Main/Restricted packages until 2030. - - Enable esm-infra with: pro enable esm-infra - - Ubuntu Pro with 'esm-apps' enabled provides security updates for - Universe/Multiverse packages until 2030. - - Enable esm-apps with: pro enable esm-apps - """ - When I run `touch -d '-2 days' /var/lib/apt/periodic/update-success-stamp` with sudo - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ package[s]? from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - The system apt information was updated 2 day\(s\) ago\. Make sure to run - sudo apt update - to get the latest package information from apt\. - - This machine is receiving security patching for Ubuntu Main/Restricted - repository until 2025. - This machine is attached to an Ubuntu Pro subscription. - - Ubuntu Pro with 'esm-infra' enabled provides security updates for - Main/Restricted packages until 2030. - - Enable esm-infra with: pro enable esm-infra - - Ubuntu Pro with 'esm-apps' enabled provides security updates for - Universe/Multiverse packages until 2030. - - Enable esm-apps with: pro enable esm-apps - """ - - # Latest released non-LTS - Scenario: Run security status in an Ubuntu machine - Given a `mantic` `lxd-container` machine with ubuntu-advantage-tools installed - When I install third-party / unknown packages in the machine - # Ansible is in esm-apps - And I apt install `ansible` - And I verify root and non-root `pro security-status` calls have the same output - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ packages from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - Main/Restricted packages receive updates until 7/2024\. - - Ubuntu Pro is not available for non-LTS releases\. - """ - When I verify root and non-root `pro security-status --esm-infra` calls have the same output - And I run `pro security-status --esm-infra` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ packages from Ubuntu Main/Restricted repository - - Main/Restricted packages receive updates until 7/2024\. - - Ubuntu Pro is not available for non-LTS releases\. - """ - When I verify root and non-root `pro security-status --esm-apps` calls have the same output - And I run `pro security-status --esm-apps` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ packages from Ubuntu Universe/Multiverse repository - - Ubuntu Pro is not available for non-LTS releases\. - """ - When I run `rm /var/lib/apt/periodic/update-success-stamp` with sudo - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ packages from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - The system apt cache may be outdated\. Make sure to run - sudo apt update - to get the latest package information from apt\. - - Main/Restricted packages receive updates until 7/2024\. - - Ubuntu Pro is not available for non-LTS releases\. - """ - When I run `touch -d '-2 days' /var/lib/apt/periodic/update-success-stamp` with sudo - And I run `pro security-status` as non-root - Then stdout matches regexp: - """ - \d+ packages installed: - +\d+ packages from Ubuntu Main/Restricted repository - +\d+ package[s]? from Ubuntu Universe/Multiverse repository - +\d+ package[s]? from a third party - +\d+ package[s]? no longer available for download - - To get more information about the packages, run - pro security-status --help - for a list of available options\. - - The system apt information was updated 2 day\(s\) ago\. Make sure to run - sudo apt update - to get the latest package information from apt\. + @uses.config.contract_token + Scenario Outline: Run security status with JSON/YAML format + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt install `ansible` + And I run `pro security-status --format json` as non-root + Then stdout is a json matching the `ua_security_status` schema + And stdout matches regexp: + """ + "_schema_version": "0.1" + """ + And stdout matches regexp: + """ + "attached": false + """ + And stdout matches regexp: + """ + "enabled_services": \[\] + """ + And stdout matches regexp: + """ + "entitled_services": \[\] + """ + And stdout matches regexp: + """ + "package": "" + """ + And stdout matches regexp: + """ + "service_name": "" + """ + And stdout matches regexp: + """ + "origin": "esm.ubuntu.com" + """ + And stdout matches regexp: + """ + "status": "pending_attach" + """ + And stdout matches regexp: + """ + "download_size": \d+ + """ + When I attach `contract_token` with sudo + And I run `pro security-status --format json` as non-root + Then stdout is a json matching the `ua_security_status` schema + Then stdout matches regexp: + """ + "_schema_version": "0.1" + """ + And stdout matches regexp: + """ + "attached": true + """ + And stdout matches regexp: + """ + "enabled_services": \["esm-apps", "esm-infra"\] + """ + And stdout matches regexp: + """ + "entitled_services": \["esm-apps", "esm-infra"\] + """ + And stdout matches regexp: + """ + "status": "upgrade_available" + """ + And stdout matches regexp: + """ + "download_size": \d+ + """ + When I run `pro security-status --format yaml` as non-root + Then stdout is a yaml matching the `ua_security_status` schema + And stdout matches regexp: + """ + _schema_version: '0.1' + """ + When I verify that running `pro security-status --format unsupported` `as non-root` exits `2` + Then I will see the following on stderr: + """ + usage: security-status [-h] [--format {json,yaml,text}] + [--thirdparty | --unavailable | --esm-infra | --esm-apps] + argument --format: invalid choice: 'unsupported' (choose from 'json', 'yaml', 'text') + """ + + Examples: ubuntu release + | release | machine_type | package | service | + | xenial | lxd-container | apport | esm-infra | + | bionic | lxd-container | ansible | esm-apps | + | bionic | wsl | ansible | esm-apps | + + @uses.config.contract_token + Scenario: Check for livepatch CVEs in security-status on an Ubuntu machine + Given a `xenial` `lxd-vm` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `pro security-status --format json` as non-root + Then stdout is a json matching the `ua_security_status` schema + Then stdout matches regexp: + """ + {"name": "cve-2013-1798", "patched": true} + """ + When I run `pro security-status --format yaml` as non-root + Then stdout is a yaml matching the `ua_security_status` schema + And stdout matches regexp: + """ + \s*- name: cve-2013-1798 + \s* patched: true + """ + + @uses.config.contract_token + Scenario: Run security status in an Ubuntu machine + Given a `xenial` `lxd-container` machine with ubuntu-advantage-tools installed + When I install third-party / unknown packages in the machine + # Ansible is in esm-apps + And I apt install `ansible` + And I verify root and non-root `pro security-status` calls have the same output + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + This machine is NOT receiving security patches because the LTS period has ended + and esm-infra is not enabled. + This machine is NOT attached to an Ubuntu Pro subscription. + + Ubuntu Pro with 'esm-infra' enabled provides security updates for + Main/Restricted packages until 2026\. There (is|are) \d+ pending security update[s]?\. + + Ubuntu Pro with 'esm-apps' enabled provides security updates for + Universe/Multiverse packages until 2026\. There (is|are) \d+ pending security update[s]?\. + + Try Ubuntu Pro with a free personal subscription on up to 5 machines. + Learn more at https://ubuntu.com/pro + """ + When I verify root and non-root `pro security-status --esm-infra` calls have the same output + And I run `pro security-status --esm-infra` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Main/Restricted repository + + This machine is NOT receiving security patches because the LTS period has ended + and esm-infra is not enabled. + + Ubuntu Pro with 'esm-infra' enabled provides security updates for + Main/Restricted packages until 2026\. There (is|are) \d+ pending security update[s]?\. + + Run 'pro help esm-infra' to learn more + + Installed packages with an available esm-infra update: + (.|\n)+ + + Further installed packages covered by esm-infra: + (.|\n)+ + + For example, run: + apt-cache show .+ + to learn more about that package\. + """ + When I verify root and non-root `pro security-status --esm-apps` calls have the same output + And I run `pro security-status --esm-apps` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + + Ubuntu Pro with 'esm-apps' enabled provides security updates for + Universe/Multiverse packages until 2026\. There (is|are) \d+ pending security update[s]?\. + + Run 'pro help esm-apps' to learn more + + Installed packages with an available esm-apps update: + (.|\n)+ + + Further installed packages covered by esm-apps: + (.|\n)+ + + For example, run: + apt-cache show .+ + to learn more about that package\. + """ + When I attach `contract_token` with sudo + And I verify root and non-root `pro security-status` calls have the same output + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + This machine is attached to an Ubuntu Pro subscription. + + Main/Restricted packages are receiving security updates from + Ubuntu Pro with 'esm-infra' enabled until 2026\. There (is|are) \d+ pending security update[s]?\. + + Universe/Multiverse packages are receiving security updates from + Ubuntu Pro with 'esm-apps' enabled until 2026\. There (is|are) \d+ pending security update[s]?\. + """ + When I verify root and non-root `pro security-status --esm-infra` calls have the same output + And I run `pro security-status --esm-infra` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ packages from Ubuntu Main/Restricted repository + + Main/Restricted packages are receiving security updates from + Ubuntu Pro with 'esm-infra' enabled until 2026\. There (is|are) \d+ pending security update[s]?\. + + Run 'pro help esm-infra' to learn more + + Installed packages with an available esm-infra update: + (.|\n)+ + + Further installed packages covered by esm-infra: + (.|\n)+ + + For example, run: + apt-cache show .+ + to learn more about that package\. + """ + When I verify root and non-root `pro security-status --esm-apps` calls have the same output + And I run `pro security-status --esm-apps` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + + Universe/Multiverse packages are receiving security updates from + Ubuntu Pro with 'esm-apps' enabled until 2026\. There (is|are) \d+ pending security update[s]?\. + + Run 'pro help esm-apps' to learn more + + Installed packages with an available esm-apps update: + (.|\n)+ + + Further installed packages covered by esm-apps: + (.|\n)+ + + For example, run: + apt-cache show .+ + to learn more about that package\. + """ + When I apt upgrade + And I verify root and non-root `pro security-status` calls have the same output + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + This machine is attached to an Ubuntu Pro subscription. + + Main/Restricted packages are receiving security updates from + Ubuntu Pro with 'esm-infra' enabled until 2026\. You have received \d+ security + update[s]?\. + + Universe/Multiverse packages are receiving security updates from + Ubuntu Pro with 'esm-apps' enabled until 2026\. You have received \d+ security + update[s]?\. + """ + When I run `pro disable esm-infra esm-apps` with sudo + And I verify root and non-root `pro security-status` calls have the same output + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + This machine is NOT receiving security patches because the LTS period has ended + and esm-infra is not enabled. + This machine is attached to an Ubuntu Pro subscription. + + Ubuntu Pro with 'esm-infra' enabled provides security updates for + Main/Restricted packages until 2026. + + Enable esm-infra with: pro enable esm-infra + + Ubuntu Pro with 'esm-apps' enabled provides security updates for + Universe/Multiverse packages until 2026. + + Enable esm-apps with: pro enable esm-apps + """ + When I verify root and non-root `pro security-status --thirdparty` calls have the same output + And I run `pro security-status --thirdparty` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +1 package from a third party + + Packages from third parties are not provided by the official Ubuntu + archive, for example packages from Personal Package Archives in Launchpad\. + + Packages: + (.|\n)+ + + For example, run: + apt-cache show .+ + to learn more about that package\. + """ + When I verify root and non-root `pro security-status --unavailable` calls have the same output + And I run `pro security-status --unavailable` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? no longer available for download + + Packages that are not available for download may be left over from a + previous release of Ubuntu, may have been installed directly from a + .deb file, or are from a source which has been disabled\. + + Packages: + (.|\n)+ + + For example, run: + apt-cache show .+ + to learn more about that package\. + """ + When I verify root and non-root `pro security-status --esm-infra` calls have the same output + And I run `pro security-status --esm-infra` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ packages from Ubuntu Main/Restricted repository + + This machine is NOT receiving security patches because the LTS period has ended + and esm-infra is not enabled. + + Ubuntu Pro with 'esm-infra' enabled provides security updates for + Main/Restricted packages until 2026. + + Run 'pro help esm-infra' to learn more + + Installed packages covered by esm-infra: + (.|\n)+ + """ + When I verify root and non-root `pro security-status --esm-apps` calls have the same output + And I run `pro security-status --esm-apps` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + + Ubuntu Pro with 'esm-apps' enabled provides security updates for + Universe/Multiverse packages until 2026. + + Run 'pro help esm-apps' to learn more + + Installed packages covered by esm-apps: + (.|\n)+ + """ + When I verify that running `pro security-status --thirdparty --unavailable` `as non-root` exits `2` + Then I will see the following on stderr + """ + usage: security-status [-h] [--format {json,yaml,text}] + [--thirdparty | --unavailable | --esm-infra | --esm-apps] + argument --unavailable: not allowed with argument --thirdparty + """ + When I run `rm /var/lib/apt/periodic/update-success-stamp` with sudo + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + The system apt cache may be outdated\. Make sure to run + sudo apt update + to get the latest package information from apt\. + + This machine is NOT receiving security patches because the LTS period has ended + and esm-infra is not enabled. + This machine is attached to an Ubuntu Pro subscription. + + Ubuntu Pro with 'esm-infra' enabled provides security updates for + Main/Restricted packages until 2026. + + Enable esm-infra with: pro enable esm-infra + + Ubuntu Pro with 'esm-apps' enabled provides security updates for + Universe/Multiverse packages until 2026. + + Enable esm-apps with: pro enable esm-apps + """ + When I run `touch -d '-2 days' /var/lib/apt/periodic/update-success-stamp` with sudo + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + The system apt information was updated 2 day\(s\) ago\. Make sure to run + sudo apt update + to get the latest package information from apt\. + + This machine is NOT receiving security patches because the LTS period has ended + and esm-infra is not enabled. + This machine is attached to an Ubuntu Pro subscription. + + Ubuntu Pro with 'esm-infra' enabled provides security updates for + Main/Restricted packages until 2026. + + Enable esm-infra with: pro enable esm-infra + + Ubuntu Pro with 'esm-apps' enabled provides security updates for + Universe/Multiverse packages until 2026. + + Enable esm-apps with: pro enable esm-apps + """ + + @uses.config.contract_token + Scenario Outline: Run security status in an Ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I install third-party / unknown packages in the machine + # Ansible is in esm-apps + And I apt install `ansible` + And I verify root and non-root `pro security-status` calls have the same output + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + This machine is receiving security patching for Ubuntu Main/Restricted + repository until 2025. + This machine is NOT attached to an Ubuntu Pro subscription. + + Ubuntu Pro with 'esm-infra' enabled provides security updates for + Main/Restricted packages until 2030. + + Ubuntu Pro with 'esm-apps' enabled provides security updates for + Universe/Multiverse packages until 2030\. There (is|are) \d+ pending security update[s]?\. + + Try Ubuntu Pro with a free personal subscription on up to 5 machines. + Learn more at https://ubuntu.com/pro + """ + When I verify root and non-root `pro security-status --esm-infra` calls have the same output + And I run `pro security-status --esm-infra` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Main/Restricted repository + + This machine is receiving security patching for Ubuntu Main/Restricted + repository until 2025. + + Ubuntu Pro with 'esm-infra' enabled provides security updates for + Main/Restricted packages until 2030. + + Run 'pro help esm-infra' to learn more + """ + When I verify root and non-root `pro security-status --esm-apps` calls have the same output + And I run `pro security-status --esm-apps` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + + Ubuntu Pro with 'esm-apps' enabled provides security updates for + Universe/Multiverse packages until 2030\. There (is|are) \d+ pending security update[s]?\. + + Run 'pro help esm-apps' to learn more + + Installed packages with an available esm-apps update: + (.|\n)+ + + Further installed packages covered by esm-apps: + (.|\n)+ + + For example, run: + apt-cache show .+ + to learn more about that package\. + """ + When I attach `contract_token` with sudo + And I verify root and non-root `pro security-status` calls have the same output + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + This machine is attached to an Ubuntu Pro subscription. + + Main/Restricted packages are receiving security updates from + Ubuntu Pro with 'esm-infra' enabled until 2030. + + Universe/Multiverse packages are receiving security updates from + Ubuntu Pro with 'esm-apps' enabled until 2030\. There (is|are) \d+ pending security update[s]?\. + """ + When I verify root and non-root `pro security-status --esm-infra` calls have the same output + And I run `pro security-status --esm-infra` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ packages from Ubuntu Main/Restricted repository + + Main/Restricted packages are receiving security updates from + Ubuntu Pro with 'esm-infra' enabled until 2030. + + Run 'pro help esm-infra' to learn more + """ + When I verify root and non-root `pro security-status --esm-apps` calls have the same output + And I run `pro security-status --esm-apps` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + + Universe/Multiverse packages are receiving security updates from + Ubuntu Pro with 'esm-apps' enabled until 2030\. There (is|are) \d+ pending security update[s]?\. + + Run 'pro help esm-apps' to learn more + + Installed packages with an available esm-apps update: + (.|\n)+ + + Further installed packages covered by esm-apps: + (.|\n)+ + + For example, run: + apt-cache show .+ + to learn more about that package\. + """ + When I apt upgrade + And I verify root and non-root `pro security-status` calls have the same output + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + This machine is attached to an Ubuntu Pro subscription. + + Main/Restricted packages are receiving security updates from + Ubuntu Pro with 'esm-infra' enabled until 2030\. + + Universe/Multiverse packages are receiving security updates from + Ubuntu Pro with 'esm-apps' enabled until 2030\. You have received \d+ security + update[s]?\. + """ + When I run `pro disable esm-infra esm-apps` with sudo + And I verify root and non-root `pro security-status` calls have the same output + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + This machine is receiving security patching for Ubuntu Main/Restricted + repository until 2025. + This machine is attached to an Ubuntu Pro subscription. + + Ubuntu Pro with 'esm-infra' enabled provides security updates for + Main/Restricted packages until 2030. + + Enable esm-infra with: pro enable esm-infra + + Ubuntu Pro with 'esm-apps' enabled provides security updates for + Universe/Multiverse packages until 2030. + + Enable esm-apps with: pro enable esm-apps + """ + When I verify root and non-root `pro security-status --thirdparty` calls have the same output + And I run `pro security-status --thirdparty` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +1 package from a third party + + Packages from third parties are not provided by the official Ubuntu + archive, for example packages from Personal Package Archives in Launchpad\. + + Packages: + (.|\n)+ + + For example, run: + apt-cache show .+ + to learn more about that package\. + """ + When I verify root and non-root `pro security-status --unavailable` calls have the same output + And I run `pro security-status --unavailable` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? no longer available for download + + Packages that are not available for download may be left over from a + previous release of Ubuntu, may have been installed directly from a + .deb file, or are from a source which has been disabled\. + + Packages: + (.|\n)+ + + For example, run: + apt-cache show .+ + to learn more about that package\. + """ + When I verify root and non-root `pro security-status --esm-infra` calls have the same output + And I run `pro security-status --esm-infra` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ packages from Ubuntu Main/Restricted repository + + This machine is receiving security patching for Ubuntu Main/Restricted + repository until 2025. + + Ubuntu Pro with 'esm-infra' enabled provides security updates for + Main/Restricted packages until 2030. + + Run 'pro help esm-infra' to learn more + """ + When I verify root and non-root `pro security-status --esm-apps` calls have the same output + And I run `pro security-status --esm-apps` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + + Ubuntu Pro with 'esm-apps' enabled provides security updates for + Universe/Multiverse packages until 2030. + + Run 'pro help esm-apps' to learn more + + Installed packages covered by esm-apps: + (.|\n)+ + """ + When I verify that running `pro security-status --thirdparty --unavailable` `as non-root` exits `2` + Then I will see the following on stderr + """ + usage: security-status [-h] [--format {json,yaml,text}] + [--thirdparty | --unavailable | --esm-infra | --esm-apps] + argument --unavailable: not allowed with argument --thirdparty + """ + When I run `rm /var/lib/apt/periodic/update-success-stamp` with sudo + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + The system apt cache may be outdated\. Make sure to run + sudo apt update + to get the latest package information from apt\. + + This machine is receiving security patching for Ubuntu Main/Restricted + repository until 2025. + This machine is attached to an Ubuntu Pro subscription. + + Ubuntu Pro with 'esm-infra' enabled provides security updates for + Main/Restricted packages until 2030. + + Enable esm-infra with: pro enable esm-infra + + Ubuntu Pro with 'esm-apps' enabled provides security updates for + Universe/Multiverse packages until 2030. + + Enable esm-apps with: pro enable esm-apps + """ + When I run `touch -d '-2 days' /var/lib/apt/periodic/update-success-stamp` with sudo + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ package[s]? from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + The system apt information was updated 2 day\(s\) ago\. Make sure to run + sudo apt update + to get the latest package information from apt\. + + This machine is receiving security patching for Ubuntu Main/Restricted + repository until 2025. + This machine is attached to an Ubuntu Pro subscription. + + Ubuntu Pro with 'esm-infra' enabled provides security updates for + Main/Restricted packages until 2030. + + Enable esm-infra with: pro enable esm-infra + + Ubuntu Pro with 'esm-apps' enabled provides security updates for + Universe/Multiverse packages until 2030. + + Enable esm-apps with: pro enable esm-apps + """ + + Examples: ubuntu release + | release | machine_type | + | focal | lxd-container | + | focal | wsl | + + # Latest released non-LTS + Scenario: Run security status in an Ubuntu machine + Given a `mantic` `lxd-container` machine with ubuntu-advantage-tools installed + When I install third-party / unknown packages in the machine + # Ansible is in esm-apps + And I apt install `ansible` + And I verify root and non-root `pro security-status` calls have the same output + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ packages from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + Main/Restricted packages receive updates until 7/2024\. + + Ubuntu Pro is not available for non-LTS releases\. + """ + When I verify root and non-root `pro security-status --esm-infra` calls have the same output + And I run `pro security-status --esm-infra` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ packages from Ubuntu Main/Restricted repository + + Main/Restricted packages receive updates until 7/2024\. + + Ubuntu Pro is not available for non-LTS releases\. + """ + When I verify root and non-root `pro security-status --esm-apps` calls have the same output + And I run `pro security-status --esm-apps` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ packages from Ubuntu Universe/Multiverse repository + + Ubuntu Pro is not available for non-LTS releases\. + """ + When I run `rm /var/lib/apt/periodic/update-success-stamp` with sudo + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ packages from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + The system apt cache may be outdated\. Make sure to run + sudo apt update + to get the latest package information from apt\. + + Main/Restricted packages receive updates until 7/2024\. + + Ubuntu Pro is not available for non-LTS releases\. + """ + When I run `touch -d '-2 days' /var/lib/apt/periodic/update-success-stamp` with sudo + And I run `pro security-status` as non-root + Then stdout matches regexp: + """ + \d+ packages installed: + +\d+ packages from Ubuntu Main/Restricted repository + +\d+ package[s]? from Ubuntu Universe/Multiverse repository + +\d+ package[s]? from a third party + +\d+ package[s]? no longer available for download + + To get more information about the packages, run + pro security-status --help + for a list of available options\. + + The system apt information was updated 2 day\(s\) ago\. Make sure to run + sudo apt update + to get the latest package information from apt\. - Main/Restricted packages receive updates until 7/2024\. + Main/Restricted packages receive updates until 7/2024\. - Ubuntu Pro is not available for non-LTS releases\. - """ + Ubuntu Pro is not available for non-LTS releases\. + """ diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/steps/airgap.py ubuntu-advantage-tools-32~16.04/features/steps/airgap.py --- ubuntu-advantage-tools-31.2.3~16.04/features/steps/airgap.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/steps/airgap.py 2024-05-10 17:07:05.000000000 +0000 @@ -1,4 +1,5 @@ import os +import re from typing import Any, Dict # noqa: F401 import yaml @@ -27,20 +28,15 @@ def extract_service_credentials(context, service, machine_name): if getattr(context, "service_mirror_cfg", None) is None: context.service_mirror_cfg = {} - - cmd = "sh -c 'echo \"{}\" | grep -A1 {} | grep -v {}'".format( - context.service_credentials, service, service - ) - when_i_run_command( - context, - cmd, - "as non-root", - machine_name=machine_name, - ) - - context.service_mirror_cfg[service.replace("-", "_")] = { - "credentials": context.process.stdout - } + credentials_pattern = r"{}:\s+(.+)".format(service) + credentials_match = re.search( + credentials_pattern, context.service_credentials + ) + if credentials_match: + extracted_credentials = credentials_match.group(1) + context.service_mirror_cfg[service.replace("-", "_")] = { + "credentials": extracted_credentials + } @when( @@ -75,6 +71,11 @@ ) apt_mirror_file += "\n" + apt_mirror_cfg + "\n" + context.service_mirror_cfg[service.replace("-", "_")]["path"] = ( + "/var/spool/apt-mirror/mirror/esm.ubuntu.com/{}/".format( + service_type + ) + ) apt_mirror_file += "clean http://archive.ubuntu.com/ubuntu" @@ -90,10 +91,8 @@ "I serve the `{service}` mirror using port `{port}` on the `{machine_name}` machine" # noqa ) def serve_apt_mirror(context, service, port, machine_name): - service_type = service.split("-")[1] - path = "/var/spool/apt-mirror/mirror/esm.ubuntu.com/{}/".format( - service_type - ) + service_type = service.replace("-", "_") + path = context.service_mirror_cfg[service_type]["path"] cmd = "nohup sh -c 'python3 -m http.server --directory {} {} > /dev/null 2>&1 &'".format( # noqa path, port ) @@ -105,7 +104,8 @@ machine_name=machine_name, ) - context.service_mirror_cfg[service.replace("-", "_")]["port"] = port + if service_type in context.service_mirror_cfg: + context.service_mirror_cfg[service_type]["port"] = port @when( @@ -120,9 +120,9 @@ "directives": { "aptURL": "http://{}:{}".format( context.machines[machine_name].instance.ip, - context.service_mirror_cfg[service.replace("-", "_")][ - "port" - ], + context.service_mirror_cfg.get( + service.replace("-", "_"), {} + ).get("port", "8000"), ) } } @@ -188,3 +188,23 @@ ) when_i_run_command(context, cmd, "with sudo", machine_name=machine_name) + + +@when( + "I consolidate `{services_list}` on a single mirror on the `{machine_name}` machine" # noqa +) # noqa +def then_i_consolidate_services_on_the_same_mirror( + context, services_list, machine_name +): + services = services_list.split(",") + all_mirrors_path = "/var/spool/apt-mirror/mirror/all-mirrors/" + + for service in services: + cmd = "rsync -a /var/spool/apt-mirror/mirror/esm.ubuntu.com/{}/ {}".format( # noqa + service.split("-")[1], all_mirrors_path + ) + when_i_run_command( + context, cmd, "with sudo", machine_name=machine_name + ) + + context.service_mirror_cfg["all_mirrors"] = {"path": all_mirrors_path} diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/steps/attach.py ubuntu-advantage-tools-32~16.04/features/steps/attach.py --- ubuntu-advantage-tools-31.2.3~16.04/features/steps/attach.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/steps/attach.py 2024-04-23 13:37:02.000000000 +0000 @@ -31,6 +31,7 @@ if ( token_type == "contract_token_staging" or token_type == "contract_token_staging_expired" + or token_type == "contract_token_staging_expired_sometimes" ): change_contract_endpoint_to_staging(context, user_spec) cmd = "pro attach {} {}".format(token, options).strip() @@ -91,3 +92,31 @@ @then("the machine is unattached") def then_the_machine_is_unattached(context): assert_that(is_machine_attached(context), equal_to(False)) + + +@when("I attach using the API") +def when_i_attach_using_the_api(context): + cmd = "pro api u.pro.attach.token.full_token_attach.v1 --args token={}".format( # noqa + context.pro_config.contract_token + ) + when_i_run_command(context, cmd, "with sudo") + + +@when("I attach using the API without enabling services") +def when_i_attach_using_the_api_withot_enabling_services(context): + cmd = ( + "pro api u.pro.attach.token.full_token_attach.v1 " + '--data \'{{"token": "{}", "auto_enable_services": false}}\'' + ) + cmd = cmd.format(context.pro_config.contract_token) + when_i_run_command(context, cmd, "with sudo") + + +@when("I attach using the API and stdin") +def when_i_attach_using_the_api_and_stdin(context): + when_i_run_command( + context, + "pro api u.pro.attach.token.full_token_attach.v1 --data -", + "with sudo", + stdin=json.dumps({"token": context.pro_config.contract_token}), + ) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/steps/contract.py ubuntu-advantage-tools-32~16.04/features/steps/contract.py --- ubuntu-advantage-tools-31.2.3~16.04/features/steps/contract.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/steps/contract.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,5 +1,7 @@ import json +import logging +import requests import yaml from behave import then, when from hamcrest import assert_that, equal_to, not_ @@ -134,3 +136,29 @@ "{ machine_token_overlay: " "/var/lib/ubuntu-advantage/machine-token-overlay.json}", ) + + +@when("I set the test contract expiration date to `{date_str}`") +def when_i_set_the_test_contract_expiration_date_to(context, date_str): + date_str = process_template_vars(context, date_str) + resp = requests.put( + "https://contracts.staging.canonical.com/v1/contract-items", + auth=( + context.pro_config.contract_staging_service_account_username, + context.pro_config.contract_staging_service_account_password, + ), + json=[ + { + # Do not delete any of these fields. They are all required. + "contractID": "cAOEJ9Vymiwr47-HZ3FnYR_YDCPILpaTfdloKpojyNPE", + "id": 39398, + "effectiveFrom": "2024-01-01T00:00:00Z", + "effectiveTo": date_str, + "metric": "units", + "reason": "contract_created", + "value": 1000, + } + ], + ) + logging.debug(resp.json()) + assert resp.status_code == 200 diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/steps/files.py ubuntu-advantage-tools-32~16.04/features/steps/files.py --- ubuntu-advantage-tools-31.2.3~16.04/features/steps/files.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/steps/files.py 2024-04-23 13:37:02.000000000 +0000 @@ -4,7 +4,7 @@ import yaml from behave import then, when -from hamcrest import assert_that, matches_regexp +from hamcrest import assert_that, equal_to, matches_regexp from features.steps.shell import when_i_run_command from features.util import SUT, process_template_vars @@ -160,3 +160,27 @@ when_i_create_file_with_content( context, dest_path, machine_name=dest_machine, text=content ) + + +@then( + "I verify that `{file_path}` is owned by `{owner}` with permission `{permission}`" # noqa +) +def then_i_verify_that_file_is_world_readable( + context, file_path, owner, permission, machine_name=SUT +): + when_i_run_command( + context, + "stat -c '%U:%G' {}".format(file_path), + "as non-root", + machine_name=machine_name, + ) + assert_that(context.process.stdout.strip(), equal_to(owner)) + + when_i_run_command( + context, + "stat -c '%a' {}".format(file_path), + "as non-root", + machine_name=machine_name, + ) + + assert_that(context.process.stdout.strip(), equal_to(permission)) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/steps/machines.py ubuntu-advantage-tools-32~16.04/features/steps/machines.py --- ubuntu-advantage-tools-31.2.3~16.04/features/steps/machines.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/steps/machines.py 2024-05-10 17:07:05.000000000 +0000 @@ -6,6 +6,7 @@ from behave import given, when from pycloudlib.instance import BaseInstance # type: ignore +from features.steps.packages import when_i_apt_install, when_i_apt_update from features.steps.shell import when_i_run_command from features.steps.ubuntu_advantage_tools import when_i_install_uat from features.util import ( @@ -106,6 +107,15 @@ machine_name=machine_name, ) + # make sure the machine has up-to-date apt data + when_i_apt_update(context, machine_name=machine_name) + + # add coverage + if context.pro_config.collect_coverage: + when_i_apt_install( + context, "python3-coverage", machine_name=machine_name + ) + if cleanup: def cleanup_instance(): @@ -211,6 +221,19 @@ "--- instance ip: {}".format(context.machines[SUT].instance.ip) ) + # There is a problem on Xenial where distro-info-data was incorrectly + # saying that Xenial was only supported until 2024. The fix was + # SRUed to Xenial, but now we need to guarantee that we run our tests + # on the latest version of that package. Additionally, we don't see + # a problem of always upgrading that package for every release. + when_i_run_command( + context, + "apt install distro-info-data -y", + "with sudo", + ) + # And this will kick of the ESM cache setup + when_i_apt_update(context) + @given( "a `{series}` `{machine_type}` machine with ubuntu-advantage-tools installed adding this cloud-init user_data" # noqa: E501 diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/steps/misc.py ubuntu-advantage-tools-32~16.04/features/steps/misc.py --- ubuntu-advantage-tools-31.2.3~16.04/features/steps/misc.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/steps/misc.py 2024-04-23 13:37:02.000000000 +0000 @@ -27,7 +27,7 @@ @when("I append the following on uaclient config") def when_i_append_to_uaclient_config(context): - cmd = "printf '{}\n' > /tmp/uaclient.conf".format(context.text) + cmd = "printf '\n{}\n' > /tmp/uaclient.conf".format(context.text) cmd = 'sh -c "{}"'.format(cmd) when_i_run_command(context, cmd, "as non-root") diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/steps/output.py ubuntu-advantage-tools-32~16.04/features/steps/output.py --- ubuntu-advantage-tools-31.2.3~16.04/features/steps/output.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/steps/output.py 2024-04-23 13:37:02.000000000 +0000 @@ -61,18 +61,90 @@ ) -@then("{stream} matches regexp") -def then_stream_matches_regexp(context, stream): - content = getattr(context.process, stream).strip() - text = process_template_vars(context, context.text) - if re.compile(text).search(content) is None: +def compare_regexp(expected_regex, actual_output): + if re.compile(expected_regex).search(actual_output) is None: raise AssertionError( "Expected to match regexp:\n{}\nBut got:\n{}".format( + textwrap.indent(expected_regex, " "), + textwrap.indent(actual_output, " "), + ) + ) + + +def process_api_data(context, api_key=None, escape=True): + json_data = json.loads(context.process.stdout.strip()) + if escape: + context.text = context.text.replace("[", "\\[") + context.text = context.text.replace("]", "\\]") + context.text = context.text.replace("(", "\\(") + context.text = context.text.replace(")", "\\)") + context.text = context.text.replace("\\n", "\\\\n") + + if api_key is None: + return json.dumps(json_data, indent=2) + else: + return json.dumps(json_data[api_key], indent=2) + + +@then("API full output matches regexp") +def then_api_output_matches_regexp(context): + content = process_api_data(context) + text = process_template_vars(context, context.text) + compare_regexp(text, content) + + +@then("API data field output matches regexp") +def then_api_data_output_matches_regexp(context): + content = process_api_data(context, api_key="data") + text = process_template_vars(context, context.text) + compare_regexp(text, content) + + +@then("API data field output is") +def then_api_data_output_is(context): + content = process_api_data(context, api_key="data", escape=False) + text = process_template_vars(context, context.text) + if not text == content: + raise AssertionError( + "Expected to find exactly:\n{}\nBut got:\n{}".format( textwrap.indent(text, " "), textwrap.indent(content, " ") ) ) +@then("API errors field output matches regexp") +def then_api_errors_output_matches_regexp(context): + content = process_api_data(context, api_key="errors") + text = process_template_vars(context, context.text) + compare_regexp(text, content) + + +@then("API errors field output is") +def then_api_errors_output_is(context): + content = process_api_data(context, api_key="errors", escape=False) + text = process_template_vars(context, context.text) + if not text == content: + raise AssertionError( + "Expected to find exactly:\n{}\nBut got:\n{}".format( + textwrap.indent(text, " "), textwrap.indent(content, " ") + ) + ) + + +@then("API warnings field output matches regexp") +def then_api_warnings_output_matches_regexp(context): + content = process_api_data(context, api_key="warnings") + text = process_template_vars(context, context.text) + compare_regexp(text, content) + + +@then("{stream} matches regexp") +def then_stream_matches_regexp(context, stream): + content = getattr(context.process, stream).strip() + text = process_template_vars(context, context.text) + compare_regexp(text, content) + + @then("{stream} contains substring") def then_stream_contains_substring(context, stream): content = getattr(context.process, stream).strip() diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/steps/packages.py ubuntu-advantage-tools-32~16.04/features/steps/packages.py --- ubuntu-advantage-tools-31.2.3~16.04/features/steps/packages.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/steps/packages.py 2024-05-10 17:07:05.000000000 +0000 @@ -16,6 +16,17 @@ ) +@when("I remove support for `{pocket}` in APT") +def when_i_remove_support_for_pocket(context, pocket): + when_i_run_command( + context, + "sed -i '/{}/d' /etc/apt/sources.list".format(pocket), + "with sudo", + ) + + when_i_apt_update(context) + + @when("I apt dist-upgrade") def when_i_dist_update(context): when_i_run_command( diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/steps/shell.py ubuntu-advantage-tools-32~16.04/features/steps/shell.py --- ubuntu-advantage-tools-31.2.3~16.04/features/steps/shell.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/steps/shell.py 2024-04-23 13:37:02.000000000 +0000 @@ -64,6 +64,16 @@ ): command = process_template_vars(context, command) prefix = get_command_prefix_for_user_spec(user_spec) + + if context.pro_config.collect_coverage: + coverage_command = ( + "python3 -m coverage run " + "--source=/usr/lib/python3/dist-packages/uaclient /usr/bin/pro" + ) + split_command = command.split() + if split_command[0] == "pro": + command = " ".join([coverage_command] + split_command[1:]) + full_cmd = prefix + shlex.split(command) if stdin is not None: diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/steps/systemd.py ubuntu-advantage-tools-32~16.04/features/steps/systemd.py --- ubuntu-advantage-tools-31.2.3~16.04/features/steps/systemd.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/steps/systemd.py 2024-04-23 13:37:02.000000000 +0000 @@ -63,7 +63,7 @@ raise AssertionError( "timer {} is not enabled or does not exist".format(timer_name) ) - match = re.match(r"^(.+) UTC\s+(.+) left", timer_info_str) + match = re.match(r"^(.+) UTC +\d+", timer_info_str) if match is None: raise AssertionError( "timer {} is not scheduled to run:\n{}".format( diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/timer.feature ubuntu-advantage-tools-32~16.04/features/timer.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/timer.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/timer.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,17 +1,19 @@ @uses.config.contract_token Feature: Timer for regular background jobs while attached - # earlies, latest lts, devel - Scenario Outline: Timer is stopped when detached, started when attached - Given a `` `` machine with ubuntu-advantage-tools installed - Then I verify the `ua-timer` systemd timer is disabled - When I attach `contract_token` with sudo - # 6 hour timer with 1 hour randomized delay -> potentially 7 hours - Then I verify the `ua-timer` systemd timer is scheduled to run within `420` minutes - When I run `pro detach --assume-yes` with sudo - Then I verify the `ua-timer` systemd timer is disabled - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | + # earlies, latest lts, devel + Scenario Outline: Timer is stopped when detached, started when attached + Given a `` `` machine with ubuntu-advantage-tools installed + Then I verify the `ua-timer` systemd timer is disabled + When I attach `contract_token` with sudo + # 6 hour timer with 1 hour randomized delay -> potentially 7 hours + Then I verify the `ua-timer` systemd timer is scheduled to run within `420` minutes + When I run `pro detach --assume-yes` with sudo + Then I verify the `ua-timer` systemd timer is disabled + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/ubuntu_pro.feature ubuntu-advantage-tools-32~16.04/features/ubuntu_pro.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/ubuntu_pro.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/ubuntu_pro.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,266 +1,277 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO image - Scenario Outline: Proxy auto-attach on a cloud Ubuntu Pro machine - Given a `` `` machine with ubuntu-advantage-tools installed - Given a `focal` `` machine named `proxy` with ingress ports `3389` - When I apt install `squid` on the `proxy` machine - And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: - """ - dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_port 3389\nhttp_access allow all - """ - And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine - # This also tests that legacy `ua_config` settings still work - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - log_level: debug - ua_config: - http_proxy: http://$behave_var{machine-ip proxy}:3389 - https_proxy: http://$behave_var{machine-ip proxy}:3389 - """ - And I verify `/var/log/squid/access.log` is empty on `proxy` machine - When I run `pro auto-attach` with sudo - Then I verify that `esm-apps` is enabled - And I verify that `esm-infra` is enabled - And I verify that `livepatch` is enabled - When I run `pro enable ` with sudo - Then I verify that `` is enabled - When I run `pro disable ` with sudo - Then stdout matches regexp: - """ - Updating package lists - """ - And I verify that `` is disabled - When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine - Then stdout matches regexp: - """ - .*CONNECT contracts.canonical.com.* - """ - And stdout does not match regexp: - """ - .*CONNECT 169.254.169.254.* - """ - And stdout does not match regexp: - """ - .*CONNECT metadata.* - """ + Scenario Outline: Proxy auto-attach on a cloud Ubuntu Pro machine + Given a `` `` machine with ubuntu-advantage-tools installed + Given a `focal` `` machine named `proxy` with ingress ports `3389` + When I apt install `squid` on the `proxy` machine + And I add this text on `/etc/squid/squid.conf` on `proxy` above `http_access deny all`: + """ + dns_v4_first on\nacl all src 0.0.0.0\/0\nhttp_port 3389\nhttp_access allow all + """ + And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine + # This also tests that legacy `ua_config` settings still work + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + log_level: debug + ua_config: + http_proxy: http://$behave_var{machine-ip proxy}:3389 + https_proxy: http://$behave_var{machine-ip proxy}:3389 + """ + And I verify `/var/log/squid/access.log` is empty on `proxy` machine + When I run `pro auto-attach` with sudo + Then I verify that `esm-apps` is enabled + And I verify that `esm-infra` is enabled + And I verify that `livepatch` is enabled + When I run `pro enable ` with sudo + Then I verify that `` is enabled + When I run `pro disable ` with sudo + Then stdout matches regexp: + """ + Updating package lists + """ + And I verify that `` is disabled + When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine + Then stdout matches regexp: + """ + .*CONNECT contracts.canonical.com.* + """ + And stdout does not match regexp: + """ + .*CONNECT 169.254.169.254.* + """ + And stdout does not match regexp: + """ + .*CONNECT metadata.* + """ - Examples: ubuntu release - | release | machine_type | fips-s | cc-eal-s | cis-s | livepatch-s | lp-desc | cis_or_usg | - | xenial | aws.pro | disabled | disabled | disabled | enabled | Canonical Livepatch service | cis | - | xenial | azure.pro | disabled | disabled | disabled | enabled | Canonical Livepatch service | cis | - | xenial | gcp.pro | n/a | disabled | disabled | warning | Current kernel is not supported | cis | - | bionic | aws.pro | disabled | disabled | disabled | enabled | Canonical Livepatch service | cis | - | bionic | azure.pro | disabled | disabled | disabled | enabled | Canonical Livepatch service | cis | - | bionic | gcp.pro | disabled | disabled | disabled | enabled | Canonical Livepatch service | cis | - | focal | aws.pro | disabled | n/a | disabled | enabled | Canonical Livepatch service | usg | - | focal | azure.pro | disabled | n/a | disabled | enabled | Canonical Livepatch service | usg | - | focal | gcp.pro | disabled | n/a | disabled | enabled | Canonical Livepatch service | usg | + Examples: ubuntu release + | release | machine_type | fips-s | cc-eal-s | cis-s | livepatch-s | lp-desc | cis_or_usg | + | xenial | aws.pro | disabled | disabled | disabled | enabled | Canonical Livepatch service | cis | + | xenial | azure.pro | disabled | disabled | disabled | enabled | Canonical Livepatch service | cis | + | xenial | gcp.pro | n/a | disabled | disabled | warning | Current kernel is not supported | cis | + | bionic | aws.pro | disabled | disabled | disabled | enabled | Canonical Livepatch service | cis | + | bionic | azure.pro | disabled | disabled | disabled | enabled | Canonical Livepatch service | cis | + | bionic | gcp.pro | disabled | disabled | disabled | enabled | Canonical Livepatch service | cis | + | focal | aws.pro | disabled | n/a | disabled | enabled | Canonical Livepatch service | usg | + | focal | azure.pro | disabled | n/a | disabled | enabled | Canonical Livepatch service | usg | + | focal | gcp.pro | disabled | n/a | disabled | enabled | Canonical Livepatch service | usg | - Scenario Outline: Attached refresh in an Ubuntu pro cloud machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - log_level: debug - """ - And I run `pro auto-attach` with sudo - Then I verify that `esm-apps` is enabled - And I verify that `esm-infra` is enabled - And I verify that `livepatch` is enabled - When I run `systemctl start ua-auto-attach.service` with sudo - And I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `0,3` - Then stdout matches regexp: - """ - .*status=0\/SUCCESS.* - """ - And stdout matches regexp: - """ - Active: inactive \(dead\).* - \s*Condition: start condition failed.* - .*ConditionPathExists=!/var/lib/ubuntu-advantage/private/machine-token.json was not met - """ - When I verify that running `pro auto-attach` `with sudo` exits `2` - Then stderr matches regexp: - """ - This machine is already attached to '.*' - To use a different subscription first run: sudo pro detach. - """ - When I run `apt-cache policy` with sudo - Then apt-cache policy for the following url has priority `510` - """ - https://esm.ubuntu.com/infra/ubuntu -infra-updates/main amd64 Packages - """ - And apt-cache policy for the following url has priority `510` - """ - https://esm.ubuntu.com/infra/ubuntu -infra-security/main amd64 Packages - """ - And apt-cache policy for the following url has priority `510` - """ - https://esm.ubuntu.com/apps/ubuntu -apps-updates/main amd64 Packages - """ - And apt-cache policy for the following url has priority `510` - """ - https://esm.ubuntu.com/apps/ubuntu -apps-security/main amd64 Packages - """ - And I ensure apt update runs without errors - When I apt install `/-infra-security` - And I run `apt-cache policy ` as non-root - Then stdout matches regexp: - """ - \s*510 https://esm.ubuntu.com/infra/ubuntu -infra-updates/main amd64 Packages - """ - And stdout matches regexp: - """ - Installed: .*[~+]esm - """ - When I apt install `/-apps-security` - And I run `apt-cache policy ` as non-root - Then stdout matches regexp: - """ - Version table: - \s*\*\*\* .* 510 - \s*510 https://esm.ubuntu.com/apps/ubuntu -apps-security/main amd64 Packages - """ - When I create the file `/var/lib/ubuntu-advantage/marker-reboot-cmds-required` with the following: - """ - """ - And I reboot the machine - And I verify that running `systemctl status ua-reboot-cmds.service` `as non-root` exits `0,3` - Then stdout matches regexp: - """ - .*status=0\/SUCCESS.* - """ - When I run `ua api u.pro.attach.auto.should_auto_attach.v1` with sudo - Then stdout matches regexp: - """ - {"_schema_version": "v1", "data": {"attributes": {"should_auto_attach": true}, "meta": {"environment_vars": \[\]}, "type": "ShouldAutoAttach"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} - """ + Scenario Outline: Attached refresh in an Ubuntu pro cloud machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + log_level: debug + """ + And I run `pro auto-attach` with sudo + Then I verify that `esm-apps` is enabled + And I verify that `esm-infra` is enabled + And I verify that `livepatch` is enabled + When I run `systemctl start ua-auto-attach.service` with sudo + And I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `0,3` + Then stdout matches regexp: + """ + .*status=0\/SUCCESS.* + """ + And stdout matches regexp: + """ + Active: inactive \(dead\).* + \s*Condition: start condition failed.* + .*ConditionPathExists=!/var/lib/ubuntu-advantage/private/machine-token.json was not met + """ + When I verify that running `pro auto-attach` `with sudo` exits `2` + Then stderr matches regexp: + """ + This machine is already attached to '.*' + To use a different subscription first run: sudo pro detach. + """ + When I run `apt-cache policy` with sudo + Then apt-cache policy for the following url has priority `510` + """ + https://esm.ubuntu.com/infra/ubuntu -infra-updates/main amd64 Packages + """ + And apt-cache policy for the following url has priority `510` + """ + https://esm.ubuntu.com/infra/ubuntu -infra-security/main amd64 Packages + """ + And apt-cache policy for the following url has priority `510` + """ + https://esm.ubuntu.com/apps/ubuntu -apps-updates/main amd64 Packages + """ + And apt-cache policy for the following url has priority `510` + """ + https://esm.ubuntu.com/apps/ubuntu -apps-security/main amd64 Packages + """ + And I ensure apt update runs without errors + When I apt install `/-infra-security` + And I run `apt-cache policy ` as non-root + Then stdout matches regexp: + """ + \s*510 https://esm.ubuntu.com/infra/ubuntu -infra-updates/main amd64 Packages + """ + And stdout matches regexp: + """ + Installed: .*[~+]esm + """ + When I apt install `/-apps-security` + And I run `apt-cache policy ` as non-root + Then stdout matches regexp: + """ + Version table: + \s*\*\*\* .* 510 + \s*510 https://esm.ubuntu.com/apps/ubuntu -apps-security/main amd64 Packages + """ + When I create the file `/var/lib/ubuntu-advantage/marker-reboot-cmds-required` with the following: + """ + """ + And I reboot the machine + And I verify that running `systemctl status ua-reboot-cmds.service` `as non-root` exits `0,3` + Then stdout matches regexp: + """ + .*status=0\/SUCCESS.* + """ + When I run `ua api u.pro.attach.auto.should_auto_attach.v1` with sudo + Then stdout matches regexp: + """ + {"_schema_version": "v1", "data": {"attributes": {"should_auto_attach": true}, "meta": {"environment_vars": \[\]}, "type": "ShouldAutoAttach"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} + """ - Examples: ubuntu release - | release | machine_type | infra-pkg | apps-pkg | - | xenial | aws.pro | libkrad0 | jq | - | xenial | azure.pro | libkrad0 | jq | - | xenial | gcp.pro | libkrad0 | jq | - | bionic | aws.pro | libkrad0 | bundler | - | bionic | azure.pro | libkrad0 | bundler | - | bionic | gcp.pro | libkrad0 | bundler | - | focal | aws.pro | hello | ant | - | focal | azure.pro | hello | ant | - | focal | gcp.pro | hello | ant | - | jammy | aws.pro | hello | hello | - | jammy | azure.pro | hello | hello | - | jammy | gcp.pro | hello | hello | + Examples: ubuntu release + | release | machine_type | infra-pkg | apps-pkg | + | xenial | aws.pro | libkrad0 | jq | + | xenial | azure.pro | libkrad0 | jq | + | xenial | gcp.pro | libkrad0 | jq | + | bionic | aws.pro | libkrad0 | bundler | + | bionic | azure.pro | libkrad0 | bundler | + | bionic | gcp.pro | libkrad0 | bundler | + | focal | aws.pro | hello | ant | + | focal | azure.pro | hello | ant | + | focal | gcp.pro | hello | ant | + | jammy | aws.pro | hello | hello | + | jammy | azure.pro | hello | hello | + | jammy | gcp.pro | hello | hello | + | noble | aws.pro | hello | hello | + | noble | azure.pro | hello | hello | + | noble | gcp.pro | hello | hello | - Scenario Outline: Auto-attach service works on Pro Machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `systemctl start ua-auto-attach.service` with sudo - And I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - data_dir: /var/lib/ubuntu-advantage - log_level: debug - log_file: /var/log/ubuntu-advantage.log - """ - And I reboot the machine - And I run `pro status --wait` with sudo - And I run `pro security-status --format json` with sudo - Then stdout matches regexp: - """ - "attached": true - """ + Scenario Outline: Auto-attach service works on Pro Machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `systemctl start ua-auto-attach.service` with sudo + And I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + data_dir: /var/lib/ubuntu-advantage + log_level: debug + log_file: /var/log/ubuntu-advantage.log + """ + And I reboot the machine + And I run `pro status --wait` with sudo + And I run `pro security-status --format json` with sudo + Then stdout matches regexp: + """ + "attached": true + """ - Examples: ubuntu release - | release | machine_type | - | xenial | aws.pro | - | xenial | azure.pro | - | xenial | gcp.pro | - | bionic | aws.pro | - | bionic | azure.pro | - | bionic | gcp.pro | - | focal | aws.pro | - | focal | azure.pro | - | focal | gcp.pro | - | jammy | aws.pro | - | jammy | azure.pro | - | jammy | gcp.pro | + Examples: ubuntu release + | release | machine_type | + | xenial | aws.pro | + | xenial | azure.pro | + | xenial | gcp.pro | + | bionic | aws.pro | + | bionic | azure.pro | + | bionic | gcp.pro | + | focal | aws.pro | + | focal | azure.pro | + | focal | gcp.pro | + | jammy | aws.pro | + | jammy | azure.pro | + | jammy | gcp.pro | + | noble | aws.pro | + | noble | azure.pro | + | noble | gcp.pro | - Scenario Outline: Auto-attach no-op when cloud-init has ubuntu_advantage on userdata - Given a `` `` machine with ubuntu-advantage-tools installed adding this cloud-init user_data: - # This user_data should not do anything, just guarantee that the ua-auto-attach service - # does nothing - """ - ubuntu_advantage: - features: - disable_auto_attach: true - """ - When I run `cloud-init query userdata` with sudo - Then stdout matches regexp: - """ - ubuntu_advantage: - features: - disable_auto_attach: true - """ - # On GCP, this service will auto-attach the machine automatically after we override - # the uaclient.conf file. To guarantee that we are not auto-attaching on reboot - # through the ua-auto-attach.service, we are masking it - When I run `systemctl mask ubuntu-advantage.service` with sudo - And I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - data_dir: /var/lib/ubuntu-advantage - log_level: debug - log_file: /var/log/ubuntu-advantage.log - """ - And I reboot the machine - And I run `pro status --wait` with sudo - And I run `pro security-status --format json` with sudo - Then stdout matches regexp: - """ - "attached": false - """ - When I run `cat /var/log/ubuntu-advantage.log` with sudo - Then stdout matches regexp: - """ - cloud-init userdata has ubuntu-advantage key. - """ - And stdout matches regexp: - """ - Skipping auto-attach and deferring to cloud-init to setup and configure auto-attach - """ - When I run `cloud-init status` with sudo - Then stdout matches regexp: - """ - status: done - """ + Scenario Outline: Auto-attach no-op when cloud-init has ubuntu_advantage on userdata + Given a `` `` machine with ubuntu-advantage-tools installed adding this cloud-init user_data: + # This user_data should not do anything, just guarantee that the ua-auto-attach service + # does nothing + """ + ubuntu_advantage: + features: + disable_auto_attach: true + """ + When I run `cloud-init query userdata` with sudo + Then stdout matches regexp: + """ + ubuntu_advantage: + features: + disable_auto_attach: true + """ + # On GCP, this service will auto-attach the machine automatically after we override + # the uaclient.conf file. To guarantee that we are not auto-attaching on reboot + # through the ua-auto-attach.service, we are masking it + When I run `systemctl mask ubuntu-advantage.service` with sudo + And I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + data_dir: /var/lib/ubuntu-advantage + log_level: debug + log_file: /var/log/ubuntu-advantage.log + """ + And I reboot the machine + And I run `pro status --wait` with sudo + And I run `pro security-status --format json` with sudo + Then stdout matches regexp: + """ + "attached": false + """ + When I run `cat /var/log/ubuntu-advantage.log` with sudo + Then stdout matches regexp: + """ + cloud-init userdata has ubuntu-advantage key. + """ + And stdout matches regexp: + """ + Skipping auto-attach and deferring to cloud-init to setup and configure auto-attach + """ + When I run `cloud-init status` with sudo + Then stdout matches regexp: + """ + status: done + """ - Examples: ubuntu release - | release | machine_type | - | xenial | aws.pro | - | xenial | azure.pro | - | xenial | gcp.pro | - | bionic | aws.pro | - | bionic | azure.pro | - | bionic | gcp.pro | - | focal | aws.pro | - | focal | azure.pro | - | focal | gcp.pro | - | jammy | aws.pro | - | jammy | azure.pro | - | jammy | gcp.pro | + Examples: ubuntu release + | release | machine_type | + | xenial | aws.pro | + | xenial | azure.pro | + | xenial | gcp.pro | + | bionic | aws.pro | + | bionic | azure.pro | + | bionic | gcp.pro | + | focal | aws.pro | + | focal | azure.pro | + | focal | gcp.pro | + | jammy | aws.pro | + | jammy | azure.pro | + | jammy | gcp.pro | + | noble | aws.pro | + | noble | azure.pro | + | noble | gcp.pro | - Scenario Outline: Unregistered Pro machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I verify that running `pro auto-attach` `with sudo` exits `1` - Then stderr matches regexp: - """ - Error on Pro Image: - missing instance information - """ + Scenario Outline: Unregistered Pro machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify that running `pro auto-attach` `with sudo` exits `1` + Then stderr matches regexp: + """ + Failed to identify this image as a valid Ubuntu Pro image. + Details: + missing instance information + """ - Examples: ubuntu release - | release | machine_type | - | xenial | aws.generic | - | bionic | aws.generic | - | focal | aws.generic | - | jammy | aws.generic | + Examples: ubuntu release + | release | machine_type | + | xenial | aws.generic | + | bionic | aws.generic | + | focal | aws.generic | + | jammy | aws.generic | + | noble | aws.generic | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/ubuntu_pro_fips.feature ubuntu-advantage-tools-32~16.04/features/ubuntu_pro_fips.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/ubuntu_pro_fips.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/ubuntu_pro_fips.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,250 +1,253 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO fips image - Scenario Outline: Check fips is enabled correctly on Ubuntu pro fips Azure machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - log_level: debug - features: - allow_xenial_fips_on_cloud: true - """ - And I run `pro auto-attach` with sudo - And I run `pro status --wait` as non-root - Then I verify that `esm-apps` is enabled - And I verify that `esm-infra` is enabled - And I verify that `fips` is enabled - And I verify that `fips-updates` is disabled - And I ensure apt update runs without errors - And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1` - When I run `uname -r` as non-root - Then stdout matches regexp: - """ - - """ - When I run `apt-cache policy ` as non-root - Then stdout does not match regexp: - """ - .*Installed: \(none\) - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ - When I run `systemctl daemon-reload` with sudo - When I run `systemctl start ua-auto-attach.service` with sudo - And I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `0,3` - Then stdout matches regexp: - """ - .*status=0\/SUCCESS.* - """ - And stdout matches regexp: - """ - Active: inactive \(dead\).* - \s*Condition: start condition failed.* - .*ConditionPathExists=!/var/lib/ubuntu-advantage/private/machine-token.json was not met - """ - When I verify that running `pro auto-attach` `with sudo` exits `2` - Then stderr matches regexp: - """ - This machine is already attached to '.*' - To use a different subscription first run: sudo pro detach. - """ - When I run `apt-cache policy` with sudo - Then apt-cache policy for the following url has priority `510` - """ - https://esm.ubuntu.com/infra/ubuntu -infra-updates/main amd64 Packages - """ - And apt-cache policy for the following url has priority `510` - """ - https://esm.ubuntu.com/infra/ubuntu -infra-security/main amd64 Packages - """ - And apt-cache policy for the following url has priority `510` - """ - https://esm.ubuntu.com/apps/ubuntu -apps-updates/main amd64 Packages - """ - And apt-cache policy for the following url has priority `510` - """ - https://esm.ubuntu.com/apps/ubuntu -apps-security/main amd64 Packages - """ - And apt-cache policy for the following url has priority `1001` - """ - amd64 Packages - """ - And I ensure apt update runs without errors - When I apt install `/-infra-security` - And I run `apt-cache policy ` as non-root - Then stdout matches regexp: - """ - \s*510 https://esm.ubuntu.com/infra/ubuntu -infra-security/main amd64 Packages - """ - Then stdout matches regexp: - """ - \s*510 https://esm.ubuntu.com/infra/ubuntu -infra-updates/main amd64 Packages - """ - And stdout matches regexp: - """ - Installed: .*[~+]esm - """ - When I apt install `/-apps-security` - And I run `apt-cache policy ` as non-root - Then stdout matches regexp: - """ - Version table: - \s*\*\*\* .* 510 - \s*510 https://esm.ubuntu.com/apps/ubuntu -apps-security/main amd64 Packages - """ - When I run `pro enable fips-updates --assume-yes` with sudo - Then I will see the following on stdout: - """ - One moment, checking your subscription first - Disabling incompatible service: FIPS - Updating FIPS Updates package lists - Installing FIPS Updates packages - Updating standard Ubuntu package lists - FIPS Updates enabled - A reboot is required to complete install. - """ - Then I verify that `fips-updates` is enabled - When I run `pro status` with sudo - Then stdout matches regexp: - """ - NOTICES - FIPS support requires system reboot to complete configuration. - """ - When I reboot the machine - And I run `uname -r` as non-root - Then stdout matches regexp: - """ - - """ - When I run `apt-cache policy ` as non-root - Then stdout does not match regexp: - """ - .*Installed: \(none\) - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ - When I run `pro status` with sudo - Then stdout does not match regexp: - """ - NOTICES - FIPS support requires system reboot to complete configuration. - """ - - Examples: ubuntu release - | release | machine_type | infra-pkg | apps-pkg | fips-apt-source | fips-kernel-version | fips-package | - | xenial | azure.pro-fips | libkrad0 | jq | https://esm.ubuntu.com/fips/ubuntu xenial/main | fips | ubuntu-fips | - | xenial | aws.pro-fips | libkrad0 | jq | https://esm.ubuntu.com/fips/ubuntu xenial/main | fips | ubuntu-fips | - | bionic | azure.pro-fips | libkrad0 | bundler | https://esm.ubuntu.com/fips/ubuntu bionic/main | azure-fips | ubuntu-azure-fips | - | bionic | aws.pro-fips | libkrad0 | bundler | https://esm.ubuntu.com/fips/ubuntu bionic/main | aws-fips | ubuntu-aws-fips | - | bionic | gcp.pro-fips | libkrad0 | bundler | https://esm.ubuntu.com/fips/ubuntu bionic/main | gcp-fips | ubuntu-gcp-fips | - | focal | azure.pro-fips | hello | 389-ds | https://esm.ubuntu.com/fips/ubuntu focal/main | azure-fips | ubuntu-azure-fips | - | focal | aws.pro-fips | hello | 389-ds | https://esm.ubuntu.com/fips/ubuntu focal/main | aws-fips | ubuntu-aws-fips | - | focal | gcp.pro-fips | hello | 389-ds | https://esm.ubuntu.com/fips/ubuntu focal/main | gcp-fips | ubuntu-gcp-fips | - - Scenario Outline: Check fips packages are correctly installed on Azure Focal machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - log_level: debug - """ - And I run `pro auto-attach` with sudo - And I run `pro status --wait` as non-root - Then I verify that `esm-apps` is enabled - And I verify that `esm-infra` is enabled - And I verify that `fips` is enabled - And I verify that `fips-updates` is disabled - And I ensure apt update runs without errors - And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1` - And I verify that `openssh-server` is installed from apt source `` - And I verify that `openssh-client` is installed from apt source `` - And I verify that `strongswan` is installed from apt source `` - And I verify that `strongswan-hmac` is installed from apt source `` - - Examples: ubuntu release - | release | machine_type | fips-apt-source | - | focal | azure.pro-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | - | focal | aws.pro-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | - | focal | gcp.pro-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | - - Scenario Outline: Check fips packages are correctly installed on Azure Bionic & Xenial machines - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - data_dir: /var/lib/ubuntu-advantage - log_level: debug - log_file: /var/log/ubuntu-advantage.log - features: - allow_xenial_fips_on_cloud: true - """ - And I run `pro auto-attach` with sudo - And I run `pro status --wait` as non-root - Then I verify that `esm-apps` is enabled - And I verify that `esm-infra` is enabled - And I verify that `fips` is enabled - And I verify that `fips-updates` is disabled - And I ensure apt update runs without errors - And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1` - And I verify that `openssh-server` is installed from apt source `` - And I verify that `openssh-client` is installed from apt source `` - And I verify that `strongswan` is installed from apt source `` - And I verify that `openssh-server-hmac` is installed from apt source `` - And I verify that `openssh-client-hmac` is installed from apt source `` - And I verify that `strongswan-hmac` is installed from apt source `` - - Examples: ubuntu release - | release | machine_type | fips-apt-source | - | xenial | azure.pro-fips | https://esm.ubuntu.com/fips/ubuntu xenial/main | - | xenial | aws.pro-fips | https://esm.ubuntu.com/fips/ubuntu xenial/main | - | bionic | azure.pro-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | - | bionic | aws.pro-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | - | bionic | gcp.pro-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | - - Scenario Outline: Check fips-updates can be enabled in a focal PRO FIPS machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: - """ - contract_url: 'https://contracts.canonical.com' - log_level: debug - """ - And I run `pro auto-attach` with sudo - And I run `pro status --wait` as non-root - Then I verify that `fips` is enabled - And I verify that `fips-updates` is disabled - When I run `pro enable fips-updates --assume-yes` with sudo - Then stdout contains substring: - """ - One moment, checking your subscription first - Disabling incompatible service: FIPS - Updating FIPS Updates package lists - Installing FIPS Updates packages - Updating standard Ubuntu package lists - FIPS Updates enabled - A reboot is required to complete install. - """ - And I verify that `fips` is disabled - And I verify that `fips-updates` is enabled - When I reboot the machine - And I run `uname -r` as non-root - Then stdout matches regexp: - """ - fips - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ - - Examples: ubuntu release - | release | machine_type | - | focal | aws.pro-fips | - | focal | azure.pro-fips | - | focal | gcp.pro-fips | + Scenario Outline: Check fips is enabled correctly on Ubuntu pro fips Azure machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + log_level: debug + features: + allow_xenial_fips_on_cloud: true + """ + And I run `pro auto-attach` with sudo + And I run `pro status --wait` as non-root + Then I verify that `esm-apps` is enabled + And I verify that `esm-infra` is enabled + And I verify that `fips` is enabled + And I verify that `fips-updates` is disabled + And I ensure apt update runs without errors + And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1` + When I run `uname -r` as non-root + Then stdout matches regexp: + """ + + """ + When I run `apt-cache policy ` as non-root + Then stdout does not match regexp: + """ + .*Installed: \(none\) + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ + When I run `systemctl daemon-reload` with sudo + When I run `systemctl start ua-auto-attach.service` with sudo + And I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `0,3` + Then stdout matches regexp: + """ + .*status=0\/SUCCESS.* + """ + And stdout matches regexp: + """ + Active: inactive \(dead\).* + \s*Condition: start condition failed.* + .*ConditionPathExists=!/var/lib/ubuntu-advantage/private/machine-token.json was not met + """ + When I verify that running `pro auto-attach` `with sudo` exits `2` + Then stderr matches regexp: + """ + This machine is already attached to '.*' + To use a different subscription first run: sudo pro detach. + """ + When I run `apt-cache policy` with sudo + Then apt-cache policy for the following url has priority `510` + """ + https://esm.ubuntu.com/infra/ubuntu -infra-updates/main amd64 Packages + """ + And apt-cache policy for the following url has priority `510` + """ + https://esm.ubuntu.com/infra/ubuntu -infra-security/main amd64 Packages + """ + And apt-cache policy for the following url has priority `510` + """ + https://esm.ubuntu.com/apps/ubuntu -apps-updates/main amd64 Packages + """ + And apt-cache policy for the following url has priority `510` + """ + https://esm.ubuntu.com/apps/ubuntu -apps-security/main amd64 Packages + """ + And apt-cache policy for the following url has priority `1001` + """ + amd64 Packages + """ + And I ensure apt update runs without errors + When I apt install `/-infra-security` + And I run `apt-cache policy ` as non-root + Then stdout matches regexp: + """ + \s*510 https://esm.ubuntu.com/infra/ubuntu -infra-security/main amd64 Packages + """ + Then stdout matches regexp: + """ + \s*510 https://esm.ubuntu.com/infra/ubuntu -infra-updates/main amd64 Packages + """ + And stdout matches regexp: + """ + Installed: .*[~+]esm + """ + When I apt install `/-apps-security` + And I run `apt-cache policy ` as non-root + Then stdout matches regexp: + """ + Version table: + \s*\*\*\* .* 510 + \s*510 https://esm.ubuntu.com/apps/ubuntu -apps-security/main amd64 Packages + """ + When I run `pro enable fips-updates --assume-yes` with sudo + Then I will see the following on stdout: + """ + One moment, checking your subscription first + Disabling incompatible service: FIPS + Removing APT access to FIPS + Updating package lists + Configuring APT access to FIPS Updates + Updating FIPS Updates package lists + Updating standard Ubuntu package lists + Installing FIPS Updates packages + FIPS Updates enabled + A reboot is required to complete install. + """ + Then I verify that `fips-updates` is enabled + When I run `pro status` with sudo + Then stdout matches regexp: + """ + NOTICES + FIPS support requires system reboot to complete configuration. + """ + When I reboot the machine + And I run `uname -r` as non-root + Then stdout matches regexp: + """ + + """ + When I run `apt-cache policy ` as non-root + Then stdout does not match regexp: + """ + .*Installed: \(none\) + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ + When I run `pro status` with sudo + Then stdout does not match regexp: + """ + NOTICES + FIPS support requires system reboot to complete configuration. + """ + + Examples: ubuntu release + | release | machine_type | infra-pkg | apps-pkg | fips-apt-source | fips-kernel-version | fips-package | + | xenial | azure.pro-fips | libkrad0 | jq | https://esm.ubuntu.com/fips/ubuntu xenial/main | fips | ubuntu-fips | + | xenial | aws.pro-fips | libkrad0 | jq | https://esm.ubuntu.com/fips/ubuntu xenial/main | fips | ubuntu-fips | + | bionic | azure.pro-fips | libkrad0 | bundler | https://esm.ubuntu.com/fips/ubuntu bionic/main | azure-fips | ubuntu-azure-fips | + | bionic | aws.pro-fips | libkrad0 | bundler | https://esm.ubuntu.com/fips/ubuntu bionic/main | aws-fips | ubuntu-aws-fips | + | bionic | gcp.pro-fips | libkrad0 | bundler | https://esm.ubuntu.com/fips/ubuntu bionic/main | gcp-fips | ubuntu-gcp-fips | + | focal | azure.pro-fips | hello | 389-ds | https://esm.ubuntu.com/fips/ubuntu focal/main | azure-fips | ubuntu-azure-fips | + | focal | aws.pro-fips | hello | 389-ds | https://esm.ubuntu.com/fips/ubuntu focal/main | aws-fips | ubuntu-aws-fips | + | focal | gcp.pro-fips | hello | 389-ds | https://esm.ubuntu.com/fips/ubuntu focal/main | gcp-fips | ubuntu-gcp-fips | + + Scenario Outline: Check fips packages are correctly installed on Azure Focal machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + log_level: debug + """ + And I run `pro auto-attach` with sudo + And I run `pro status --wait` as non-root + Then I verify that `esm-apps` is enabled + And I verify that `esm-infra` is enabled + And I verify that `fips` is enabled + And I verify that `fips-updates` is disabled + And I ensure apt update runs without errors + And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1` + And I verify that `openssh-server` is installed from apt source `` + And I verify that `openssh-client` is installed from apt source `` + And I verify that `strongswan` is installed from apt source `` + And I verify that `strongswan-hmac` is installed from apt source `` + + Examples: ubuntu release + | release | machine_type | fips-apt-source | + | focal | azure.pro-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | + | focal | aws.pro-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | + | focal | gcp.pro-fips | https://esm.ubuntu.com/fips/ubuntu focal/main | + + Scenario Outline: Check fips packages are correctly installed on Azure Bionic & Xenial machines + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + data_dir: /var/lib/ubuntu-advantage + log_level: debug + log_file: /var/log/ubuntu-advantage.log + features: + allow_xenial_fips_on_cloud: true + """ + And I run `pro auto-attach` with sudo + And I run `pro status --wait` as non-root + Then I verify that `esm-apps` is enabled + And I verify that `esm-infra` is enabled + And I verify that `fips` is enabled + And I verify that `fips-updates` is disabled + And I ensure apt update runs without errors + And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1` + And I verify that `openssh-server` is installed from apt source `` + And I verify that `openssh-client` is installed from apt source `` + And I verify that `strongswan` is installed from apt source `` + And I verify that `openssh-server-hmac` is installed from apt source `` + And I verify that `openssh-client-hmac` is installed from apt source `` + And I verify that `strongswan-hmac` is installed from apt source `` + + Examples: ubuntu release + | release | machine_type | fips-apt-source | + | xenial | azure.pro-fips | https://esm.ubuntu.com/fips/ubuntu xenial/main | + | xenial | aws.pro-fips | https://esm.ubuntu.com/fips/ubuntu xenial/main | + | bionic | azure.pro-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | + | bionic | aws.pro-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | + | bionic | gcp.pro-fips | https://esm.ubuntu.com/fips/ubuntu bionic/main | + + Scenario Outline: Check fips-updates can be enabled in a focal PRO FIPS machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following: + """ + contract_url: 'https://contracts.canonical.com' + log_level: debug + """ + And I run `pro auto-attach` with sudo + And I run `pro status --wait` as non-root + Then I verify that `fips` is enabled + And I verify that `fips-updates` is disabled + When I run `pro enable fips-updates --assume-yes` with sudo + Then stdout contains substring: + """ + One moment, checking your subscription first + Disabling incompatible service: FIPS + Updating FIPS Updates package lists + Installing FIPS Updates packages + Updating standard Ubuntu package lists + FIPS Updates enabled + A reboot is required to complete install. + """ + And I verify that `fips` is disabled + And I verify that `fips-updates` is enabled + When I reboot the machine + And I run `uname -r` as non-root + Then stdout matches regexp: + """ + fips + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ + + Examples: ubuntu release + | release | machine_type | + | focal | aws.pro-fips | + | focal | azure.pro-fips | + | focal | gcp.pro-fips | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/ubuntu_upgrade.feature ubuntu-advantage-tools-32~16.04/features/ubuntu_upgrade.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/ubuntu_upgrade.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/ubuntu_upgrade.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,124 +1,123 @@ @uses.config.contract_token Feature: Upgrade between releases when uaclient is attached - @slow - @upgrade - Scenario Outline: Attached upgrade - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I run `` with sudo - # Local PPAs are prepared and served only when testing with local debs - And I prepare the local PPAs to upgrade from `` to `` - And I run `DEBIAN_FRONTEND=noninteractive apt-get dist-upgrade --assume-yes` with sudo - # Some packages upgrade may require a reboot - And I reboot the machine - And I create the file `/etc/update-manager/release-upgrades.d/ua-test.cfg` with the following - """ - [Sources] - AllowThirdParty=yes - """ - And I run `sed -i 's/Prompt=lts/Prompt=/' /etc/update-manager/release-upgrades` with sudo - And I run `do-release-upgrade --frontend DistUpgradeViewNonInteractive` `with sudo` and stdin `y\n` - And I reboot the machine - And I run `lsb_release -cs` as non-root - Then I will see the following on stdout: - """ - - """ - And I verify that running `egrep "|disabled" /etc/apt/sources.list.d/*` `as non-root` exits `2` - And I will see the following on stdout: - """ - """ - When I run `pro refresh` with sudo - And I run `pro status --all` with sudo - Then stdout matches regexp: - """ - +yes + - """ - Then stdout matches regexp: - """ - +yes + - """ - When I run `pro detach --assume-yes` with sudo - Then stdout matches regexp: - """ - This machine is now detached. - """ + @slow @upgrade + Scenario Outline: Attached upgrade + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I run `` with sudo + # Local PPAs are prepared and served only when testing with local debs + And I prepare the local PPAs to upgrade from `` to `` + And I run `DEBIAN_FRONTEND=noninteractive apt-get dist-upgrade --assume-yes` with sudo + # Some packages upgrade may require a reboot + And I reboot the machine + And I create the file `/etc/update-manager/release-upgrades.d/ua-test.cfg` with the following + """ + [Sources] + AllowThirdParty=yes + """ + And I run `sed -i 's/Prompt=lts/Prompt=/' /etc/update-manager/release-upgrades` with sudo + And I run `do-release-upgrade --frontend DistUpgradeViewNonInteractive` `with sudo` and stdin `y\n` + And I reboot the machine + And I run `lsb_release -cs` as non-root + Then I will see the following on stdout: + """ + + """ + And I verify that running `egrep "|disabled" /etc/apt/sources.list.d/*` `as non-root` exits `2` + And I will see the following on stdout: + """ + """ + When I run `pro refresh` with sudo + And I run `pro status --all` with sudo + Then stdout matches regexp: + """ + +yes + + """ + Then stdout matches regexp: + """ + +yes + + """ + When I run `pro detach --assume-yes` with sudo + Then stdout matches regexp: + """ + This machine is now detached. + """ - Examples: ubuntu release - | release | machine_type | next_release | prompt | devel_release | service1 | service1_status | service2 | service2_status | before_cmd | - | xenial | lxd-container | bionic | lts | | esm-infra | enabled | esm-apps | enabled | true | - | bionic | lxd-container | focal | lts | | esm-infra | enabled | esm-apps | enabled | true | - | bionic | lxd-container | focal | lts | | usg | enabled | usg | enabled | pro enable cis | - | focal | lxd-container | jammy | lts | | esm-infra | enabled | esm-apps | enabled | true | - | jammy | lxd-container | mantic | normal | | esm-infra | n/a | esm-apps | n/a | true | - | mantic | lxd-container | noble | normal | --devel-release | esm-infra | n/a | esm-apps | n/a | true | + Examples: ubuntu release + | release | machine_type | next_release | prompt | devel_release | service1 | service1_status | service2 | service2_status | before_cmd | + | xenial | lxd-container | bionic | lts | | esm-infra | enabled | esm-apps | enabled | true | + | bionic | lxd-container | focal | lts | | esm-infra | enabled | esm-apps | enabled | true | + | bionic | lxd-container | focal | lts | | usg | enabled | usg | enabled | pro enable cis | + | focal | lxd-container | jammy | lts | | esm-infra | enabled | esm-apps | enabled | true | + | jammy | lxd-container | mantic | normal | | esm-infra | n/a | esm-apps | n/a | true | + | mantic | lxd-container | noble | normal | --devel-release | esm-infra | disabled | esm-apps | disabled | true | + | jammy | lxd-container | noble | normal | --devel-release | esm-infra | enabled | esm-apps | enabled | true | - @slow - @upgrade - Scenario Outline: Attached FIPS upgrade across LTS releases - Given a `` `` machine with ubuntu-advantage-tools installed - When I attach `contract_token` with sudo - And I apt install `lsof` - And I run `pro disable livepatch` with sudo - And I run `pro enable --assume-yes` with sudo - Then stdout contains substring: - """ - Updating package lists - Installing packages - Updating standard Ubuntu package lists - enabled - A reboot is required to complete install. - """ - When I run `pro status --all` with sudo - Then I verify that `` is enabled - And I ensure apt update runs without errors - When I reboot the machine - And I run `uname -r` as non-root - Then stdout matches regexp: - """ - fips - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ - # Local PPAs are prepared and served only when testing with local debs - When I prepare the local PPAs to upgrade from `` to `` - And I run `DEBIAN_FRONTEND=noninteractive apt-get dist-upgrade -y --allow-downgrades` with sudo - # A package may need a reboot after running dist-upgrade - And I reboot the machine - And I create the file `/etc/update-manager/release-upgrades.d/ua-test.cfg` with the following - """ - [Sources] - AllowThirdParty=yes - """ - Then I verify that running `do-release-upgrade --frontend DistUpgradeViewNonInteractive` `with sudo` exits `0` - When I reboot the machine - And I run `lsb_release -cs` as non-root - Then I will see the following on stdout: - """ - - """ - When I verify that running `egrep "disabled" /etc/apt/sources.list.d/.list` `as non-root` exits `1` - Then I will see the following on stdout: - """ - """ - When I run `pro status --all` with sudo - Then I verify that `` is enabled - When I run `uname -r` as non-root - Then stdout matches regexp: - """ - fips - """ - When I run `cat /proc/sys/crypto/fips_enabled` with sudo - Then I will see the following on stdout: - """ - 1 - """ + @slow @upgrade + Scenario Outline: Attached FIPS upgrade across LTS releases + Given a `` `` machine with ubuntu-advantage-tools installed + When I attach `contract_token` with sudo + And I apt install `lsof` + And I run `pro disable livepatch` with sudo + And I run `pro enable --assume-yes` with sudo + Then stdout contains substring: + """ + Updating package lists + Installing packages + Updating standard Ubuntu package lists + enabled + A reboot is required to complete install. + """ + When I run `pro status --all` with sudo + Then I verify that `` is enabled + And I ensure apt update runs without errors + When I reboot the machine + And I run `uname -r` as non-root + Then stdout matches regexp: + """ + fips + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ + # Local PPAs are prepared and served only when testing with local debs + When I prepare the local PPAs to upgrade from `` to `` + And I run `DEBIAN_FRONTEND=noninteractive apt-get dist-upgrade -y --allow-downgrades` with sudo + # A package may need a reboot after running dist-upgrade + And I reboot the machine + And I create the file `/etc/update-manager/release-upgrades.d/ua-test.cfg` with the following + """ + [Sources] + AllowThirdParty=yes + """ + Then I verify that running `do-release-upgrade --frontend DistUpgradeViewNonInteractive` `with sudo` exits `0` + When I reboot the machine + And I run `lsb_release -cs` as non-root + Then I will see the following on stdout: + """ + + """ + When I verify that running `egrep "disabled" /etc/apt/sources.list.d/.list` `as non-root` exits `1` + Then I will see the following on stdout: + """ + """ + When I run `pro status --all` with sudo + Then I verify that `` is enabled + When I run `uname -r` as non-root + Then stdout matches regexp: + """ + fips + """ + When I run `cat /proc/sys/crypto/fips_enabled` with sudo + Then I will see the following on stdout: + """ + 1 + """ - Examples: ubuntu release - | release | machine_type | next_release | fips-service | fips-name | source-file | - | xenial | lxd-vm | bionic | fips | FIPS | ubuntu-fips | - | xenial | lxd-vm | bionic | fips-updates | FIPS Updates | ubuntu-fips-updates | + Examples: ubuntu release + | release | machine_type | next_release | fips-service | fips-name | source-file | + | xenial | lxd-vm | bionic | fips | FIPS | ubuntu-fips | + | xenial | lxd-vm | bionic | fips-updates | FIPS Updates | ubuntu-fips-updates | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/ubuntu_upgrade_unattached.feature ubuntu-advantage-tools-32~16.04/features/ubuntu_upgrade_unattached.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/ubuntu_upgrade_unattached.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/ubuntu_upgrade_unattached.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,67 +1,66 @@ Feature: Upgrade between releases when uaclient is unattached - @slow - @upgrade - @uses.config.contract_token - Scenario Outline: Unattached upgrade - Given a `` `` machine with ubuntu-advantage-tools installed - # Local PPAs are prepared and served only when testing with local debs - When I prepare the local PPAs to upgrade from `` to `` - # in case this still exists - And I delete the file `/var/lib/ubuntu-advantage/apt-esm/etc/apt/sources.list.d/ubuntu-esm-infra.list` - And I apt update - And I run `sleep 30` as non-root - And I run shell command `cat /var/lib/ubuntu-advantage/apt-esm/etc/apt/sources.list.d/ubuntu-esm-infra.sources || true` with sudo - Then if `` in `xenial or bionic` and stdout matches regexp: - """ - Types: deb - URIs: https://esm.ubuntu.com/infra/ubuntu - Suites: -infra-security -infra-updates - Components: main - Signed-By: /usr/share/keyrings/ubuntu-pro-esm-infra.gpg - """ - When I apt dist-upgrade - # Some packages upgrade may require a reboot - And I reboot the machine - And I create the file `/etc/update-manager/release-upgrades.d/ua-test.cfg` with the following - """ - [Sources] - AllowThirdParty=yes - """ - And I run `sed -i 's/Prompt=lts/Prompt=/' /etc/update-manager/release-upgrades` with sudo - And I run `do-release-upgrade --frontend DistUpgradeViewNonInteractive` `with sudo` and stdin `y\n` - And I reboot the machine - And I run `lsb_release -cs` as non-root - Then I will see the following on stdout: - """ - - """ - And I verify that running `egrep "|disabled" /etc/apt/sources.list.d/*` `as non-root` exits `2` - And I will see the following on stdout: - """ - """ - And I verify that the folder `/var/lib/ubuntu-advantage/apt-esm` does not exist - When I apt update - And I run shell command `cat /var/lib/ubuntu-advantage/apt-esm/etc/apt/sources.list.d/ubuntu-esm-apps.sources || true` with sudo - Then if `` not in `mantic or noble` and stdout matches regexp: - """ - Types: deb - URIs: https://esm.ubuntu.com/apps/ubuntu - Suites: -apps-security -apps-updates - Components: main - Signed-By: /usr/share/keyrings/ubuntu-pro-esm-apps.gpg - """ - When I attach `contract_token` with sudo - And I run `pro status --all` with sudo - Then stdout matches regexp: - """ - esm-infra +yes + - """ + @slow @upgrade @uses.config.contract_token + Scenario Outline: Unattached upgrade + Given a `` `` machine with ubuntu-advantage-tools installed + # Local PPAs are prepared and served only when testing with local debs + When I prepare the local PPAs to upgrade from `` to `` + # in case this still exists + And I delete the file `/var/lib/ubuntu-advantage/apt-esm/etc/apt/sources.list.d/ubuntu-esm-infra.list` + And I apt update + And I run `sleep 30` as non-root + And I run shell command `cat /var/lib/ubuntu-advantage/apt-esm/etc/apt/sources.list.d/ubuntu-esm-infra.sources || true` with sudo + Then if `` in `xenial or bionic` and stdout matches regexp: + """ + Types: deb + URIs: https://esm.ubuntu.com/infra/ubuntu + Suites: -infra-security -infra-updates + Components: main + Signed-By: /usr/share/keyrings/ubuntu-pro-esm-infra.gpg + """ + When I apt dist-upgrade + # Some packages upgrade may require a reboot + And I reboot the machine + And I create the file `/etc/update-manager/release-upgrades.d/ua-test.cfg` with the following + """ + [Sources] + AllowThirdParty=yes + """ + And I run `sed -i 's/Prompt=lts/Prompt=/' /etc/update-manager/release-upgrades` with sudo + And I run `do-release-upgrade --frontend DistUpgradeViewNonInteractive` `with sudo` and stdin `y\n` + And I reboot the machine + And I run `lsb_release -cs` as non-root + Then I will see the following on stdout: + """ + + """ + And I verify that running `egrep "|disabled" /etc/apt/sources.list.d/*` `as non-root` exits `2` + And I will see the following on stdout: + """ + """ + And I verify that the folder `/var/lib/ubuntu-advantage/apt-esm` does not exist + When I apt update + And I run shell command `cat /var/lib/ubuntu-advantage/apt-esm/etc/apt/sources.list.d/ubuntu-esm-apps.sources || true` with sudo + Then if `` not in `mantic or noble` and stdout matches regexp: + """ + Types: deb + URIs: https://esm.ubuntu.com/apps/ubuntu + Suites: -apps-security -apps-updates + Components: main + Signed-By: /usr/share/keyrings/ubuntu-pro-esm-apps.gpg + """ + When I attach `contract_token` with sudo + And I run `pro status --all` with sudo + Then stdout matches regexp: + """ + esm-infra +yes + + """ - Examples: ubuntu release - | release | machine_type | next_release | prompt | devel_release | service_status | - | xenial | lxd-container | bionic | lts | | enabled | - | bionic | lxd-container | focal | lts | | enabled | - | focal | lxd-container | jammy | lts | | enabled | - | jammy | lxd-container | mantic | normal | | n/a | - | mantic | lxd-container | noble | normal | --devel-release | n/a | + Examples: ubuntu release + | release | machine_type | next_release | prompt | devel_release | service_status | + | xenial | lxd-container | bionic | lts | | enabled | + | bionic | lxd-container | focal | lts | | enabled | + | focal | lxd-container | jammy | lts | | enabled | + | jammy | lxd-container | mantic | normal | | n/a | + | mantic | lxd-container | noble | normal | --devel-release | enabled | + | jammy | lxd-container | noble | normal | --devel-release | enabled | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/unattached_commands.feature ubuntu-advantage-tools-32~16.04/features/unattached_commands.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/unattached_commands.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/unattached_commands.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,510 +1,568 @@ Feature: Command behaviour when unattached - Scenario Outline: Unattached auto-attach does nothing in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - # Validate systemd unit/timer syntax - When I run `systemd-analyze verify /lib/systemd/system/ua-timer.timer` with sudo - Then stderr does not match regexp: - """ - .*\/lib\/systemd/system\/ua.* - """ - When I verify that running `pro auto-attach` `as non-root` exits `1` - Then stderr matches regexp: - """ - This command must be run as root \(try using sudo\). - """ - When I run `pro auto-attach` with sudo - Then stderr matches regexp: - """ - Auto-attach image support is not available on lxd - See: https://canonical-ubuntu-pro-client.readthedocs-hosted.com/en/latest/explanations/what_are_ubuntu_pro_cloud_instances.html - """ - - Examples: ubuntu release - | release | machine_type | - | bionic | lxd-container | - | focal | lxd-container | - | xenial | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - Scenario Outline: Unattached commands that requires enabled user in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I verify that running `pro ` `as non-root` exits `1` - Then I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - When I verify that running `pro ` `with sudo` exits `1` - Then stderr matches regexp: - """ - This machine is not attached to an Ubuntu Pro subscription. - See https://ubuntu.com/pro - """ - - Examples: pro commands - | release | machine_type | command | - | bionic | lxd-container | detach | - | bionic | lxd-container | refresh | - | focal | lxd-container | detach | - | focal | lxd-container | refresh | - | xenial | lxd-container | detach | - | xenial | lxd-container | refresh | - | jammy | lxd-container | detach | - | jammy | lxd-container | refresh | - | mantic | lxd-container | detach | - | mantic | lxd-container | refresh | - - Scenario Outline: Help command on an unattached machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro help esm-infra` as non-root - Then I will see the following on stdout: - """ - Name: - esm-infra - - Available: - - - Help: - Expanded Security Maintenance for Infrastructure provides access to a private - PPA which includes available high and critical CVE fixes for Ubuntu LTS - packages in the Ubuntu Main repository between the end of the standard Ubuntu - LTS security maintenance and its end of life. It is enabled by default with - Ubuntu Pro. You can find out more about the service at - https://ubuntu.com/security/esm - """ - When I run `pro help esm-infra --format json` with sudo - Then I will see the following on stdout: - """ - {"name": "esm-infra", "available": "", "help": "Expanded Security Maintenance for Infrastructure provides access to a private\nPPA which includes available high and critical CVE fixes for Ubuntu LTS\npackages in the Ubuntu Main repository between the end of the standard Ubuntu\nLTS security maintenance and its end of life. It is enabled by default with\nUbuntu Pro. You can find out more about the service at\nhttps://ubuntu.com/security/esm"} - """ - When I verify that running `pro help invalid-service` `with sudo` exits `1` - Then I will see the following on stderr: - """ - No help available for 'invalid-service' - """ - When I verify that running `pro --wrong-flag` `with sudo` exits `2` - Then I will see the following on stderr: - """ - usage: pro [flags] - Try 'pro --help' for more information. - """ - - Examples: ubuntu release - | release | machine_type | infra-available | - | xenial | lxd-container | yes | - | bionic | lxd-container | yes | - | focal | lxd-container | yes | - | jammy | lxd-container | yes | - | mantic | lxd-container | no | - - Scenario Outline: Unattached enable/disable fails in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I verify that running `pro esm-infra` `as non-root` exits `1` - Then I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - When I verify that running `pro esm-infra` `with sudo` exits `1` - Then I will see the following on stderr: - """ - To use 'esm-infra' you need an Ubuntu Pro subscription - Personal and community subscriptions are available at no charge - See https://ubuntu.com/pro - """ - When I verify that running `pro esm-infra --format json --assume-yes` `with sudo` exits `1` - Then stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"additional_info": {"valid_service": "esm-infra"}, "message": "To use 'esm-infra' you need an Ubuntu Pro subscription\nPersonal and community subscriptions are available at no charge\nSee https://ubuntu.com/pro", "message_code": "valid-service-failure-unattached", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - When I verify that running `pro unknown` `as non-root` exits `1` - Then I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - When I verify that running `pro unknown` `with sudo` exits `1` - Then I will see the following on stderr: - """ - Cannot unknown service 'unknown'. - """ - When I verify that running `pro unknown --format json --assume-yes` `with sudo` exits `1` - Then stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"additional_info": {"invalid_service": "unknown", "operation": "", "service_msg": ""}, "message": "Cannot unknown service 'unknown'.\n", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - When I verify that running `pro esm-infra unknown` `as non-root` exits `1` - Then I will see the following on stderr: - """ - This command must be run as root (try using sudo). - """ - When I verify that running `pro esm-infra unknown` `with sudo` exits `1` - Then I will see the following on stderr: - """ - Cannot unknown service 'unknown'. - - To use 'esm-infra' you need an Ubuntu Pro subscription - Personal and community subscriptions are available at no charge - See https://ubuntu.com/pro - """ - When I verify that running `pro esm-infra unknown --format json --assume-yes` `with sudo` exits `1` - Then stdout is a json matching the `ua_operation` schema - And I will see the following on stdout: - """ - {"_schema_version": "0.1", "errors": [{"additional_info": {"invalid_service": "unknown", "operation": "", "service_msg": "", "valid_service": "esm-infra"}, "message": "Cannot unknown service 'unknown'.\n\nTo use 'esm-infra' you need an Ubuntu Pro subscription\nPersonal and community subscriptions are available at no charge\nSee https://ubuntu.com/pro", "message_code": "mixed-services-failure-unattached", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} - """ - - Examples: ubuntu release - | release | machine_type | command | - | xenial | lxd-container | enable | - | xenial | lxd-container | disable | - | bionic | lxd-container | enable | - | bionic | lxd-container | disable | - | focal | lxd-container | enable | - | focal | lxd-container | disable | - | jammy | lxd-container | enable | - | jammy | lxd-container | disable | - | mantic | lxd-container | enable | - | mantic | lxd-container | disable | - - Scenario Outline: Check for newer versions of the client in an ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - # Make sure we have a fresh, just rebooted, environment - When I reboot the machine - Then I verify that no files exist matching `/run/ubuntu-advantage/candidate-version` - When I run `pro status` with sudo - Then stderr does not match regexp: - """ - .*\[info\].* A new version is available: 2:99.9.9 - Please run: - sudo apt install ubuntu-pro-client - to get the latest bug fixes and new features. - """ - And I verify that files exist matching `/run/ubuntu-advantage/candidate-version` - # We forge a candidate to see results - When I delete the file `/run/ubuntu-advantage/candidate-version` - And I create the file `/run/ubuntu-advantage/candidate-version` with the following - """ - 2:99.9.9 - """ - And I run `pro status` as non-root - Then stderr matches regexp: - """ - .*\[info\].* A new version is available: 2:99.9.9 - Please run: - sudo apt install ubuntu-pro-client - to get the latest bug fixes and new features. - """ - When I run `pro status --format json` as non-root - Then stderr does not match regexp: - """ - .*\[info\].* A new version is available: 2:99.9.9 - Please run: - sudo apt install ubuntu-pro-client - to get the latest bug fixes and new features. - """ - When I run `pro config show` as non-root - Then stderr matches regexp: - """ - .*\[info\].* A new version is available: 2:99.9.9 - Please run: - sudo apt install ubuntu-pro-client - to get the latest bug fixes and new features. - """ - When I run `pro api u.pro.version.v1` as non-root - Then stdout matches regexp - """ - \"code\": \"new-version-available\" - """ - When I verify that running `pro api u.pro.version.inexistent` `as non-root` exits `1` - Then stdout matches regexp - """ - \"code\": \"new-version-available\" - """ - When I run `pro api u.pro.version.v1` as non-root - Then stderr does not match regexp: - """ - .*\[info\].* A new version is available: 2:99.9.9 - Please run: - sudo apt install ubuntu-pro-client - to get the latest bug fixes and new features. - """ - When I apt update - # The update will bring a new candidate, which is the current installed version - And I run `pro status` as non-root - Then stderr does not match regexp: - """ - .*\[info\].* A new version is available: 2:99.9.9 - Please run: - sudo apt install ubuntu-pro-client - to get the latest bug fixes and new features. - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - # Side effect: this verifies that `ua` still works as a command - Scenario Outline: Verify autocomplete options - Given a `` `` machine with ubuntu-advantage-tools installed - When I prepare the autocomplete test - And I press tab twice to autocomplete the `ua` command - Then stdout matches regexp: - """ - --debug +auto-attach +enable +status\r - --help +collect-logs +fix +system\r - --version +config +help +version\r - api +detach +refresh +\r - attach +disable +security-status - """ - When I press tab twice to autocomplete the `pro` command - Then stdout matches regexp: - """ - --debug +auto-attach +enable +status\r - --help +collect-logs +fix +system\r - --version +config +help +version\r - api +detach +refresh +\r - attach +disable +security-status - """ - When I press tab twice to autocomplete the `ua enable` command - Then stdout matches regexp: - """ - anbox-cloud +esm-infra +livepatch +usg\s* - cc-eal +fips +realtime-kernel\s* - cis +fips-updates +ros\s* - esm-apps +landscape +ros-updates\s* - """ - When I press tab twice to autocomplete the `pro enable` command - Then stdout matches regexp: - """ - anbox-cloud +esm-infra +livepatch +usg\s* - cc-eal +fips +realtime-kernel\s* - cis +fips-updates +ros\s* - esm-apps +landscape +ros-updates\s* - """ - When I press tab twice to autocomplete the `ua disable` command - Then stdout matches regexp: - """ - anbox-cloud +esm-infra +livepatch +usg\s* - cc-eal +fips +realtime-kernel\s* - cis +fips-updates +ros\s* - esm-apps +landscape +ros-updates\s* - """ - When I press tab twice to autocomplete the `pro disable` command - Then stdout matches regexp: - """ - anbox-cloud +esm-infra +livepatch +usg\s* - cc-eal +fips +realtime-kernel\s* - cis +fips-updates +ros\s* - esm-apps +landscape +ros-updates\s* - """ - - Examples: ubuntu release - | release | machine_type | - # | xenial | lxd-container | Can't rely on Xenial because of bash sorting things weirdly - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - Scenario Outline: esm cache failures don't generate errors - Given a `` `` machine with ubuntu-advantage-tools installed - When I disable access to esm.ubuntu.com - And I apt update - # Wait for the hook to fail - When I wait `5` seconds - And I run `systemctl --failed` with sudo - Then stdout does not match regexp: - """ - esm-cache\.service - """ - When I run `journalctl -o cat -u esm-cache.service` with sudo - Then stdout does not match regexp: - """ - raise FetchFailedException\(\) - """ - When I run `ls /var/crash/` with sudo - Then stdout does not match regexp: - """ - _usr_lib_ubuntu-advantage_esm_cache - """ - When I run `cat /var/log/ubuntu-advantage.log` with sudo - Then stdout matches regexp: - """ - Failed to fetch ESM Apt Cache item: - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - - # Services fail, degraded systemctl, but no crashes. - Scenario Outline: services fail gracefully when yaml is broken/absent - Given a `` `` machine with ubuntu-advantage-tools installed - When I apt update - And I run `rm -rf /usr/lib/python3/dist-packages/yaml` with sudo - And I verify that running `pro status` `with sudo` exits `1` - Then stderr matches regexp: - """ - Couldn't import the YAML module. - Make sure the 'python3-yaml' package is installed correctly - and \/usr\/lib\/python3\/dist-packages is in your PYTHONPATH\. - """ - When I verify that running `python3 /usr/lib/ubuntu-advantage/esm_cache.py` `with sudo` exits `1` - Then stderr matches regexp: - """ - Couldn't import the YAML module. - Make sure the 'python3-yaml' package is installed correctly - and \/usr\/lib\/python3\/dist-packages is in your PYTHONPATH\. - """ - When I verify that running `systemctl start apt-news.service` `with sudo` exits `1` - And I verify that running `systemctl start esm-cache.service` `with sudo` exits `1` - And I run `systemctl --failed` with sudo - Then stdout matches regexp: - """ - apt-news.service - """ - And stdout matches regexp: - """ - esm-cache.service - """ - When I apt install `python3-pip` - And I run `pip3 install pyyaml==3.10 ` with sudo - And I run `ls /usr/local/lib//dist-packages/` with sudo - Then stdout matches regexp: - """ - yaml - """ - And I verify that running `pro status` `with sudo` exits `1` - Then stderr matches regexp: - """ - Error while trying to parse a yaml file using 'yaml' from - """ - # Test the specific script which triggered LP #2007241 - When I verify that running `python3 /usr/lib/ubuntu-advantage/esm_cache.py` `with sudo` exits `1` - Then stderr matches regexp: - """ - Error while trying to parse a yaml file using 'yaml' from - """ - When I verify that running `systemctl start apt-news.service` `with sudo` exits `1` - And I verify that running `systemctl start esm-cache.service` `with sudo` exits `1` - And I run `systemctl --failed` with sudo - Then stdout matches regexp: - """ - apt-news.service - """ - And stdout matches regexp: - """ - esm-cache.service - """ - When I run `ls /var/crash` with sudo - Then I will see the following on stdout - """ - """ - - Examples: ubuntu release - | release | machine_type | python_version | suffix | - | jammy | lxd-container | python3.10 | | - # mantic+ has a BIG error message explaining why this is a clear user error... - | mantic | lxd-container | python3.11 | --break-system-packages | - - - Scenario Outline: Warn users not to redirect/pipe human readable output - Given a `` `` machine with ubuntu-advantage-tools installed - When I run shell command `pro version | cat` as non-root - Then I will see the following on stderr - """ - """ - When I run shell command `pro version > version_out` as non-root - Then I will see the following on stderr - """ - """ - When I run shell command `pro status | cat` as non-root - Then I will see the following on stderr - """ - WARNING: this output is intended to be human readable, and subject to change. - In scripts, prefer using machine readable data from the `pro api` command, - or use `pro status --format json`. - """ - When I run shell command `pro status | cat` with sudo - Then I will see the following on stderr - """ - WARNING: this output is intended to be human readable, and subject to change. - In scripts, prefer using machine readable data from the `pro api` command, - or use `pro status --format json`. - """ - When I run shell command `pro status > status_out` as non-root - Then I will see the following on stderr - """ - WARNING: this output is intended to be human readable, and subject to change. - In scripts, prefer using machine readable data from the `pro api` command, - or use `pro status --format json`. - """ - When I run shell command `pro status > status_out` with sudo - Then I will see the following on stderr - """ - WARNING: this output is intended to be human readable, and subject to change. - In scripts, prefer using machine readable data from the `pro api` command, - or use `pro status --format json`. - """ - When I run shell command `pro status --format tabular | cat` as non-root - Then I will see the following on stderr - """ - WARNING: this output is intended to be human readable, and subject to change. - In scripts, prefer using machine readable data from the `pro api` command, - or use `pro status --format json`. - """ - When I run shell command `pro status --format tabular > status_out` as non-root - Then I will see the following on stderr - """ - WARNING: this output is intended to be human readable, and subject to change. - In scripts, prefer using machine readable data from the `pro api` command, - or use `pro status --format json`. - """ - When I run shell command `pro status --format json | cat` as non-root - Then I will see the following on stderr - """ - """ - When I run shell command `pro status --format json > status_out` as non-root - Then I will see the following on stderr - """ - """ - When I run shell command `pro security-status | cat` as non-root - Then I will see the following on stderr - """ - WARNING: this output is intended to be human readable, and subject to change. - In scripts, prefer using machine readable data from the `pro api` command, - or use `pro security-status --format json`. - """ - When I run shell command `pro security-status > status_out` as non-root - Then I will see the following on stderr - """ - WARNING: this output is intended to be human readable, and subject to change. - In scripts, prefer using machine readable data from the `pro api` command, - or use `pro security-status --format json`. - """ - When I run shell command `pro security-status --format json | cat` as non-root - Then I will see the following on stderr - """ - """ - When I run shell command `pro security-status --format json > status_out` as non-root - Then I will see the following on stderr - """ - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - | focal | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | + Scenario Outline: Unattached auto-attach does nothing in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + # Validate systemd unit/timer syntax + When I run `systemd-analyze verify /lib/systemd/system/ua-timer.timer` with sudo + Then stderr does not match regexp: + """ + .*\/lib\/systemd/system\/ua.* + """ + When I verify that running `pro auto-attach` `as non-root` exits `1` + Then stderr matches regexp: + """ + This command must be run as root \(try using sudo\). + """ + When I run `pro auto-attach` with sudo + Then stderr matches regexp: + """ + Auto-attach image support is not available on lxd + See: https://canonical-ubuntu-pro-client.readthedocs-hosted.com/en/latest/explanations/what_are_ubuntu_pro_cloud_instances.html + """ + + Examples: ubuntu release + | release | machine_type | + | bionic | lxd-container | + | focal | lxd-container | + | xenial | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: Unattached commands that requires enabled user in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify that running `pro ` `as non-root` exits `1` + Then I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + When I verify that running `pro ` `with sudo` exits `1` + Then stderr matches regexp: + """ + This machine is not attached to an Ubuntu Pro subscription. + See https://ubuntu.com/pro + """ + + Examples: pro commands + | release | machine_type | command | + | bionic | lxd-container | detach | + | bionic | lxd-container | refresh | + | bionic | wsl | detach | + | bionic | wsl | refresh | + | focal | lxd-container | detach | + | focal | lxd-container | refresh | + | focal | wsl | detach | + | focal | wsl | refresh | + | xenial | lxd-container | detach | + | xenial | lxd-container | refresh | + | jammy | lxd-container | detach | + | jammy | lxd-container | refresh | + | jammy | wsl | detach | + | jammy | wsl | refresh | + | mantic | lxd-container | detach | + | mantic | lxd-container | refresh | + | noble | lxd-container | detach | + | noble | lxd-container | refresh | + + Scenario Outline: Help command on an unattached machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro help esm-infra` as non-root + Then I will see the following on stdout: + """ + Name: + esm-infra + + Available: + + + Help: + Expanded Security Maintenance for Infrastructure provides access to a private + PPA which includes available high and critical CVE fixes for Ubuntu LTS + packages in the Ubuntu Main repository between the end of the standard Ubuntu + LTS security maintenance and its end of life. It is enabled by default with + Ubuntu Pro. You can find out more about the service at + https://ubuntu.com/security/esm + """ + When I run `pro help esm-infra --format json` with sudo + Then I will see the following on stdout: + """ + {"name": "esm-infra", "available": "", "help": "Expanded Security Maintenance for Infrastructure provides access to a private\nPPA which includes available high and critical CVE fixes for Ubuntu LTS\npackages in the Ubuntu Main repository between the end of the standard Ubuntu\nLTS security maintenance and its end of life. It is enabled by default with\nUbuntu Pro. You can find out more about the service at\nhttps://ubuntu.com/security/esm"} + """ + When I verify that running `pro help invalid-service` `with sudo` exits `1` + Then I will see the following on stderr: + """ + No help available for 'invalid-service' + """ + When I verify that running `pro --wrong-flag` `with sudo` exits `2` + Then I will see the following on stderr: + """ + usage: pro [flags] + Try 'pro --help' for more information. + """ + + Examples: ubuntu release + | release | machine_type | infra-available | + | xenial | lxd-container | yes | + | bionic | lxd-container | yes | + | bionic | wsl | yes | + | focal | lxd-container | yes | + | focal | wsl | yes | + | jammy | lxd-container | yes | + | jammy | wsl | yes | + | mantic | lxd-container | no | + | noble | lxd-container | yes | + + Scenario Outline: Unattached enable/disable fails in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify that running `pro esm-infra` `as non-root` exits `1` + Then I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + When I verify that running `pro esm-infra` `with sudo` exits `1` + Then I will see the following on stderr: + """ + Cannot services when unattached - nothing to do. + To use 'esm-infra' you need an Ubuntu Pro subscription. + Personal and community subscriptions are available at no charge. + See https://ubuntu.com/pro + """ + When I verify that running `pro esm-infra --format json --assume-yes` `with sudo` exits `1` + Then stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"additional_info": {"operation": "", "valid_service": "esm-infra"}, "message": "Cannot services when unattached - nothing to do.\nTo use 'esm-infra' you need an Ubuntu Pro subscription.\nPersonal and community subscriptions are available at no charge.\nSee https://ubuntu.com/pro", "message_code": "valid-service-failure-unattached", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + When I verify that running `pro unknown` `as non-root` exits `1` + Then I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + When I verify that running `pro unknown` `with sudo` exits `1` + Then I will see the following on stderr: + """ + Cannot unknown service 'unknown'. + """ + When I verify that running `pro unknown --format json --assume-yes` `with sudo` exits `1` + Then stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"additional_info": {"invalid_service": "unknown", "operation": "", "service_msg": ""}, "message": "Cannot unknown service 'unknown'.\n", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + When I verify that running `pro esm-infra unknown` `as non-root` exits `1` + Then I will see the following on stderr: + """ + This command must be run as root (try using sudo). + """ + When I verify that running `pro esm-infra unknown` `with sudo` exits `1` + Then I will see the following on stderr: + """ + Cannot unknown service 'unknown'. + + Cannot services when unattached - nothing to do. + To use 'esm-infra' you need an Ubuntu Pro subscription. + Personal and community subscriptions are available at no charge. + See https://ubuntu.com/pro + """ + When I verify that running `pro esm-infra unknown --format json --assume-yes` `with sudo` exits `1` + Then stdout is a json matching the `ua_operation` schema + And I will see the following on stdout: + """ + {"_schema_version": "0.1", "errors": [{"additional_info": {"invalid_service": "unknown", "operation": "", "service_msg": "", "valid_service": "esm-infra"}, "message": "Cannot unknown service 'unknown'.\n\nCannot services when unattached - nothing to do.\nTo use 'esm-infra' you need an Ubuntu Pro subscription.\nPersonal and community subscriptions are available at no charge.\nSee https://ubuntu.com/pro", "message_code": "mixed-services-failure-unattached", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []} + """ + + Examples: ubuntu release + | release | machine_type | command | + | xenial | lxd-container | enable | + | xenial | lxd-container | disable | + | bionic | lxd-container | enable | + | bionic | lxd-container | disable | + | bionic | wsl | enable | + | bionic | wsl | disable | + | focal | lxd-container | enable | + | focal | lxd-container | disable | + | focal | wsl | enable | + | focal | wsl | disable | + | jammy | lxd-container | enable | + | jammy | lxd-container | disable | + | jammy | wsl | enable | + | jammy | wsl | disable | + | mantic | lxd-container | enable | + | mantic | lxd-container | disable | + | noble | lxd-container | enable | + | noble | lxd-container | disable | + + Scenario Outline: Check for newer versions of the client in an ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + # Make sure we have a fresh, just rebooted, environment + When I reboot the machine + Then I verify that no files exist matching `/run/ubuntu-advantage/candidate-version` + When I run `pro status` with sudo + Then stderr does not match regexp: + """ + .*\[info\].* A new version is available: 2:99.9.9 + Please run: + sudo apt install ubuntu-pro-client + to get the latest bug fixes and new features. + """ + And I verify that files exist matching `/run/ubuntu-advantage/candidate-version` + # We forge a candidate to see results + When I delete the file `/run/ubuntu-advantage/candidate-version` + And I create the file `/run/ubuntu-advantage/candidate-version` with the following + """ + 2:99.9.9 + """ + And I run `pro status` as non-root + Then stderr matches regexp: + """ + .*\[info\].* A new version is available: 2:99.9.9 + Please run: + sudo apt install ubuntu-pro-client + to get the latest bug fixes and new features. + """ + When I run `pro status --format json` as non-root + Then stderr does not match regexp: + """ + .*\[info\].* A new version is available: 2:99.9.9 + Please run: + sudo apt install ubuntu-pro-client + to get the latest bug fixes and new features. + """ + When I run `pro config show` as non-root + Then stderr matches regexp: + """ + .*\[info\].* A new version is available: 2:99.9.9 + Please run: + sudo apt install ubuntu-pro-client + to get the latest bug fixes and new features. + """ + When I run `pro api u.pro.version.v1` as non-root + Then stdout matches regexp + """ + \"code\": \"new-version-available\" + """ + When I verify that running `pro api u.pro.version.inexistent` `as non-root` exits `1` + Then stdout matches regexp + """ + \"code\": \"new-version-available\" + """ + When I run `pro api u.pro.version.v1` as non-root + Then stderr does not match regexp: + """ + .*\[info\].* A new version is available: 2:99.9.9 + Please run: + sudo apt install ubuntu-pro-client + to get the latest bug fixes and new features. + """ + When I apt update + # The update will bring a new candidate, which is the current installed version + And I run `pro status` as non-root + Then stderr does not match regexp: + """ + .*\[info\].* A new version is available: 2:99.9.9 + Please run: + sudo apt install ubuntu-pro-client + to get the latest bug fixes and new features. + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + # Side effect: this verifies that `ua` still works as a command + Scenario Outline: Verify autocomplete options + Given a `` `` machine with ubuntu-advantage-tools installed + When I prepare the autocomplete test + And I press tab twice to autocomplete the `ua` command + Then stdout matches regexp: + """ + --debug +auto-attach +enable +status\r + --help +collect-logs +fix +system\r + --version +config +help +version\r + api +detach +refresh +\r + attach +disable +security-status + """ + When I press tab twice to autocomplete the `pro` command + Then stdout matches regexp: + """ + --debug +auto-attach +enable +status\r + --help +collect-logs +fix +system\r + --version +config +help +version\r + api +detach +refresh +\r + attach +disable +security-status + """ + When I press tab twice to autocomplete the `ua enable` command + Then stdout matches regexp: + """ + anbox-cloud +esm-infra +livepatch +usg\s* + cc-eal +fips +realtime-kernel\s* + cis +fips-updates +ros\s* + esm-apps +landscape +ros-updates\s* + """ + When I press tab twice to autocomplete the `pro enable` command + Then stdout matches regexp: + """ + anbox-cloud +esm-infra +livepatch +usg\s* + cc-eal +fips +realtime-kernel\s* + cis +fips-updates +ros\s* + esm-apps +landscape +ros-updates\s* + """ + When I press tab twice to autocomplete the `ua disable` command + Then stdout matches regexp: + """ + anbox-cloud +esm-infra +livepatch +usg\s* + cc-eal +fips +realtime-kernel\s* + cis +fips-updates +ros\s* + esm-apps +landscape +ros-updates\s* + """ + When I press tab twice to autocomplete the `pro disable` command + Then stdout matches regexp: + """ + anbox-cloud +esm-infra +livepatch +usg\s* + cc-eal +fips +realtime-kernel\s* + cis +fips-updates +ros\s* + esm-apps +landscape +ros-updates\s* + """ + + Examples: ubuntu release + | release | machine_type | + # | xenial | lxd-container | Can't rely on Xenial because of bash sorting things weirdly + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: esm cache failures don't generate errors + Given a `` `` machine with ubuntu-advantage-tools installed + When I disable access to esm.ubuntu.com + And I apt update + # Wait for the hook to fail + When I wait `5` seconds + And I run `systemctl --failed` with sudo + Then stdout does not match regexp: + """ + esm-cache\.service + """ + When I run `journalctl -o cat -u esm-cache.service` with sudo + Then stdout does not contain substring: + """ + raise FetchFailedException() + """ + Then stdout matches regexp: + """ + "WARNING", "ubuntupro.apt", "fail", \d+, "Failed to fetch ESM Apt Cache item: https://esm.ubuntu.com/apps/ubuntu/dists/-apps-security/InRelease", {}] + """ + When I run `ls /var/crash/` with sudo + Then stdout does not contain substring: + """ + _usr_lib_ubuntu-advantage_esm_cache + """ + + Examples: ubuntu release + | release | machine_type | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | noble | lxd-container | + + # Duplicating just for xenial so we disable it on GH + # See GH: #3013 + @no_gh + Scenario Outline: esm cache failures don't generate errors on xenial + Given a `` `` machine with ubuntu-advantage-tools installed + When I disable access to esm.ubuntu.com + And I apt update + # Wait for the hook to fail + When I wait `5` seconds + And I run `systemctl --failed` with sudo + Then stdout does not match regexp: + """ + esm-cache\.service + """ + When I run `journalctl -o cat -u esm-cache.service` with sudo + Then stdout does not contain substring: + """ + raise FetchFailedException() + """ + Then stdout matches regexp: + """ + "WARNING", "ubuntupro.apt", "fail", \d+, "Failed to fetch ESM Apt Cache item: https://esm.ubuntu.com/apps/ubuntu/dists/-apps-security/InRelease", {}] + """ + When I run `ls /var/crash/` with sudo + Then stdout does not contain substring: + """ + _usr_lib_ubuntu-advantage_esm_cache + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + + # Services fail, degraded systemctl, but no crashes. + Scenario Outline: services fail gracefully when yaml is broken/absent + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `rm -rf /usr/lib/python3/dist-packages/yaml` with sudo + And I verify that running `pro status` `with sudo` exits `1` + Then stderr matches regexp: + """ + Couldn't import the YAML module. + Make sure the 'python3-yaml' package is installed correctly + and \/usr\/lib\/python3\/dist-packages is in your PYTHONPATH\. + """ + When I verify that running `python3 /usr/lib/ubuntu-advantage/esm_cache.py` `with sudo` exits `1` + Then stderr matches regexp: + """ + Couldn't import the YAML module. + Make sure the 'python3-yaml' package is installed correctly + and \/usr\/lib\/python3\/dist-packages is in your PYTHONPATH\. + """ + When I verify that running `systemctl start apt-news.service` `with sudo` exits `1` + And I verify that running `systemctl start esm-cache.service` `with sudo` exits `1` + And I run `systemctl --failed` with sudo + Then stdout matches regexp: + """ + apt-news.service + """ + And stdout matches regexp: + """ + esm-cache.service + """ + When I apt install `python3-pip` + And I run `pip3 install pyyaml==3.10 ` with sudo + And I run `ls /usr/local/lib//dist-packages/` with sudo + Then stdout matches regexp: + """ + yaml + """ + And I verify that running `pro status` `with sudo` exits `1` + Then stderr matches regexp: + """ + Error while trying to parse a yaml file using 'yaml' from + """ + # Test the specific script which triggered LP #2007241 + When I verify that running `python3 /usr/lib/ubuntu-advantage/esm_cache.py` `with sudo` exits `1` + Then stderr matches regexp: + """ + Error while trying to parse a yaml file using 'yaml' from + """ + When I verify that running `systemctl start apt-news.service` `with sudo` exits `1` + And I verify that running `systemctl start esm-cache.service` `with sudo` exits `1` + And I run `systemctl --failed` with sudo + Then stdout matches regexp: + """ + apt-news.service + """ + And stdout matches regexp: + """ + esm-cache.service + """ + When I run `ls /var/crash` with sudo + Then I will see the following on stdout + """ + """ + + Examples: ubuntu release + | release | machine_type | python_version | suffix | + | jammy | lxd-container | python3.10 | | + # mantic has a BIG error message explaining why this is a clear user error... + | mantic | lxd-container | python3.11 | --break-system-packages | + + # noble doesn't even allow --break-system-packages to work + # | noble | lxd-container | python3.11 | --break-system-packages | + Scenario Outline: Warn users not to redirect/pipe human readable output + Given a `` `` machine with ubuntu-advantage-tools installed + When I run shell command `pro version | cat` as non-root + Then I will see the following on stderr + """ + """ + When I run shell command `pro version > version_out` as non-root + Then I will see the following on stderr + """ + """ + When I run shell command `pro status | cat` as non-root + Then I will see the following on stderr + """ + WARNING: this output is intended to be human readable, and subject to change. + In scripts, prefer using machine readable data from the `pro api` command, + or use `pro status --format json`. + """ + When I run shell command `pro status | cat` with sudo + Then I will see the following on stderr + """ + WARNING: this output is intended to be human readable, and subject to change. + In scripts, prefer using machine readable data from the `pro api` command, + or use `pro status --format json`. + """ + When I run shell command `pro status > status_out` as non-root + Then I will see the following on stderr + """ + WARNING: this output is intended to be human readable, and subject to change. + In scripts, prefer using machine readable data from the `pro api` command, + or use `pro status --format json`. + """ + When I run shell command `pro status > status_out` with sudo + Then I will see the following on stderr + """ + WARNING: this output is intended to be human readable, and subject to change. + In scripts, prefer using machine readable data from the `pro api` command, + or use `pro status --format json`. + """ + When I run shell command `pro status --format tabular | cat` as non-root + Then I will see the following on stderr + """ + WARNING: this output is intended to be human readable, and subject to change. + In scripts, prefer using machine readable data from the `pro api` command, + or use `pro status --format json`. + """ + When I run shell command `pro status --format tabular > status_out` as non-root + Then I will see the following on stderr + """ + WARNING: this output is intended to be human readable, and subject to change. + In scripts, prefer using machine readable data from the `pro api` command, + or use `pro status --format json`. + """ + When I run shell command `pro status --format json | cat` as non-root + Then I will see the following on stderr + """ + """ + When I run shell command `pro status --format json > status_out` as non-root + Then I will see the following on stderr + """ + """ + When I run shell command `pro security-status | cat` as non-root + Then I will see the following on stderr + """ + WARNING: this output is intended to be human readable, and subject to change. + In scripts, prefer using machine readable data from the `pro api` command, + or use `pro security-status --format json`. + """ + When I run shell command `pro security-status > status_out` as non-root + Then I will see the following on stderr + """ + WARNING: this output is intended to be human readable, and subject to change. + In scripts, prefer using machine readable data from the `pro api` command, + or use `pro security-status --format json`. + """ + When I run shell command `pro security-status --format json | cat` as non-root + Then I will see the following on stderr + """ + """ + When I run shell command `pro security-status --format json > status_out` as non-root + Then I will see the following on stderr + """ + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/features/unattached_status.feature ubuntu-advantage-tools-32~16.04/features/unattached_status.feature --- ubuntu-advantage-tools-31.2.3~16.04/features/unattached_status.feature 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/features/unattached_status.feature 2024-04-23 13:37:02.000000000 +0000 @@ -1,601 +1,757 @@ Feature: Unattached status - Scenario Outline: Unattached status in a ubuntu machine - formatted - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `pro status --format json` as non-root - Then stdout is a json matching the `ua_status` schema - When I run `pro status --format yaml` as non-root - Then stdout is a yaml matching the `ua_status` schema - When I run `sed -i 's/contracts.can/invalidurl.notcan/' /etc/ubuntu-advantage/uaclient.conf` with sudo - And I verify that running `pro status --format json` `as non-root` exits `1` - Then stdout is a json matching the `ua_status` schema - And stdout matches regexp: - """ - {"environment_vars": \[\], "errors": \[{"message": "Failed to connect to .*\\n\[Errno -2\] Name or service not known\\n", "message_code": "connectivity-error", "service": null, "type": "system"}\], "result": "failure", "services": \[\], "warnings": \[\]} - """ - And I verify that running `pro status --format yaml` `as non-root` exits `1` - Then stdout is a yaml matching the `ua_status` schema - And stdout matches regexp: - """ - environment_vars: \[\] - errors: - - message: 'Failed to connect to https://invalidurl.notcanonical.com/v1/resources(.*) - - \[Errno -2\] Name or service not known - - ' - message_code: connectivity-error - service: null - type: system - result: failure - services: \[\] - warnings: \[\] - """ - - Examples: ubuntu release - | release | machine_type | - | bionic | lxd-container | - | focal | lxd-container | - | xenial | lxd-container | - | jammy | lxd-container | - | mantic | lxd-container | - - Scenario Outline: Unattached status in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I verify root and non-root `pro status` calls have the same output - And I run `pro status` as non-root - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +DESCRIPTION - (anbox-cloud +(yes|no) +.*)? - ?cc-eal +yes +Common Criteria EAL2 Provisioning Packages - cis +yes +Security compliance and audit tools - esm-apps +yes +Expanded Security Maintenance for Applications - esm-infra +yes +Expanded Security Maintenance for Infrastructure - fips +yes +NIST-certified FIPS crypto packages - fips-updates +yes +FIPS compliant crypto packages with stable security updates - livepatch +yes +(Canonical Livepatch service|Current kernel is not supported) - ros +yes +Security Updates for the Robot Operating System - ros-updates +yes +All Updates for the Robot Operating System - - For a list of all Ubuntu Pro services, run 'pro status --all' - - This machine is not attached to an Ubuntu Pro subscription. - See https://ubuntu.com/pro - """ - When I verify root and non-root `pro status --all` calls have the same output - And I run `pro status --all` as non-root - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +DESCRIPTION - anbox-cloud +(yes|no) +.* - cc-eal +yes +Common Criteria EAL2 Provisioning Packages - cis +yes +Security compliance and audit tools - esm-apps +yes +Expanded Security Maintenance for Applications - esm-infra +yes +Expanded Security Maintenance for Infrastructure - fips +yes +NIST-certified FIPS crypto packages - fips-preview +no +.* - fips-updates +yes +FIPS compliant crypto packages with stable security updates - landscape +no +Management and administration tool for Ubuntu - livepatch +yes +(Canonical Livepatch service|Current kernel is not supported) - realtime-kernel +no +Ubuntu kernel with PREEMPT_RT patches integrated - ros +yes +Security Updates for the Robot Operating System - ros-updates +yes +All Updates for the Robot Operating System - - This machine is not attached to an Ubuntu Pro subscription. - See https://ubuntu.com/pro - """ - When I append the following on uaclient config: - """ - features: - allow_beta: true - """ - And I verify root and non-root `pro status` calls have the same output - And I run `pro status` as non-root - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +DESCRIPTION - (anbox-cloud +(yes|no) +.*)? - ?cc-eal +yes +Common Criteria EAL2 Provisioning Packages - cis +yes +Security compliance and audit tools - esm-apps +yes +Expanded Security Maintenance for Applications - esm-infra +yes +Expanded Security Maintenance for Infrastructure - fips +yes +NIST-certified FIPS crypto packages - fips-updates +yes +FIPS compliant crypto packages with stable security updates - livepatch +yes +(Canonical Livepatch service|Current kernel is not supported) - ros +yes +Security Updates for the Robot Operating System - ros-updates +yes +All Updates for the Robot Operating System - - FEATURES - allow_beta: True - - For a list of all Ubuntu Pro services, run 'pro status --all' - - This machine is not attached to an Ubuntu Pro subscription. - See https://ubuntu.com/pro - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - - Scenario Outline: Unattached status in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I verify root and non-root `pro status` calls have the same output - When I run `pro status` as non-root - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +DESCRIPTION - anbox-cloud +yes +.* - esm-apps +yes +Expanded Security Maintenance for Applications - esm-infra +yes +Expanded Security Maintenance for Infrastructure - fips +yes +NIST-certified FIPS crypto packages - fips-updates +yes +FIPS compliant crypto packages with stable security updates - livepatch +yes +Canonical Livepatch service - ros +yes +Security Updates for the Robot Operating System - usg +yes +Security compliance and audit tools - - For a list of all Ubuntu Pro services, run 'pro status --all' - - This machine is not attached to an Ubuntu Pro subscription. - See https://ubuntu.com/pro - """ - When I verify root and non-root `pro status --all` calls have the same output - And I run `pro status --all` as non-root - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +DESCRIPTION - anbox-cloud +yes +.* - cc-eal +no +Common Criteria EAL2 Provisioning Packages - esm-apps +yes +Expanded Security Maintenance for Applications - esm-infra +yes +Expanded Security Maintenance for Infrastructure - fips +yes +NIST-certified FIPS crypto packages - fips-preview +no +.* - fips-updates +yes +FIPS compliant crypto packages with stable security updates - landscape +no +Management and administration tool for Ubuntu - livepatch +yes +Canonical Livepatch service - realtime-kernel +no +Ubuntu kernel with PREEMPT_RT patches integrated - ros +yes +Security Updates for the Robot Operating System - ros-updates +no +All Updates for the Robot Operating System - usg +yes +Security compliance and audit tools - - This machine is not attached to an Ubuntu Pro subscription. - See https://ubuntu.com/pro - """ - When I append the following on uaclient config: - """ - features: - allow_beta: true - """ - When I verify root and non-root `pro status` calls have the same output - And I run `pro status` as non-root - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +DESCRIPTION - anbox-cloud +yes +.* - esm-apps +yes +Expanded Security Maintenance for Applications - esm-infra +yes +Expanded Security Maintenance for Infrastructure - fips +yes +NIST-certified FIPS crypto packages - fips-updates +yes +FIPS compliant crypto packages with stable security updates - livepatch +yes +Canonical Livepatch service - ros +yes +Security Updates for the Robot Operating System - usg +yes +Security compliance and audit tools - - FEATURES - allow_beta: True - - For a list of all Ubuntu Pro services, run 'pro status --all' - - This machine is not attached to an Ubuntu Pro subscription. - See https://ubuntu.com/pro - """ - - Examples: ubuntu release - | release | machine_type | - | focal | lxd-container | - - Scenario Outline: Unattached status in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I verify root and non-root `pro status` calls have the same output - And I run `pro status` as non-root - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +DESCRIPTION - anbox-cloud +yes +.* - esm-apps +yes +Expanded Security Maintenance for Applications - esm-infra +yes +Expanded Security Maintenance for Infrastructure - fips-preview +yes +.* - fips-updates +yes +FIPS compliant crypto packages with stable security updates - livepatch +yes +Canonical Livepatch service - realtime-kernel +yes +Ubuntu kernel with PREEMPT_RT patches integrated - usg +yes +Security compliance and audit tools - - For a list of all Ubuntu Pro services, run 'pro status --all' - - This machine is not attached to an Ubuntu Pro subscription. - See https://ubuntu.com/pro - """ - When I verify root and non-root `pro status --all` calls have the same output - And I run `pro status --all` as non-root - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +DESCRIPTION - anbox-cloud +yes +.* - cc-eal +no +Common Criteria EAL2 Provisioning Packages - esm-apps +yes +Expanded Security Maintenance for Applications - esm-infra +yes +Expanded Security Maintenance for Infrastructure - fips +no +NIST-certified FIPS crypto packages - fips-preview +yes +.* - fips-updates +yes +FIPS compliant crypto packages with stable security updates - landscape +no +Management and administration tool for Ubuntu - livepatch +yes +Canonical Livepatch service - realtime-kernel +yes +Ubuntu kernel with PREEMPT_RT patches integrated - ros +no +Security Updates for the Robot Operating System - ros-updates +no +All Updates for the Robot Operating System - usg +yes +Security compliance and audit tools - - This machine is not attached to an Ubuntu Pro subscription. - See https://ubuntu.com/pro - """ - When I append the following on uaclient config: - """ - features: - allow_beta: true - """ - When I verify root and non-root `pro status` calls have the same output - And I run `pro status` as non-root - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +DESCRIPTION - anbox-cloud +yes +.* - esm-apps +yes +Expanded Security Maintenance for Applications - esm-infra +yes +Expanded Security Maintenance for Infrastructure - fips-preview +yes +.* - fips-updates +yes +FIPS compliant crypto packages with stable security updates - livepatch +yes +Canonical Livepatch service - realtime-kernel +yes +Ubuntu kernel with PREEMPT_RT patches integrated - usg +yes +Security compliance and audit tools - - FEATURES - allow_beta: True - - For a list of all Ubuntu Pro services, run 'pro status --all' - - This machine is not attached to an Ubuntu Pro subscription. - See https://ubuntu.com/pro - """ - - Examples: ubuntu release - | release | machine_type | - | jammy | lxd-container | - - @uses.config.contract_token - Scenario Outline: Simulate status in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I do a preflight check for `contract_token` without the all flag - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION - (anbox-cloud +yes +.*)? - ?cc-eal +yes +yes +no +Common Criteria EAL2 Provisioning Packages - cis +yes +yes +no +Security compliance and audit tools - esm-apps +yes +yes +yes +Expanded Security Maintenance for Applications - esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure - fips +yes +yes +no +NIST-certified FIPS crypto packages - fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates - livepatch +yes +yes +yes +Canonical Livepatch service - """ - When I do a preflight check for `contract_token` with the all flag - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION - anbox-cloud +(yes|no) +.* - cc-eal +yes +yes +no +Common Criteria EAL2 Provisioning Packages - cis +yes +yes +no +Security compliance and audit tools - esm-apps +yes +yes +yes +Expanded Security Maintenance for Applications - esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure - fips +yes +yes +no +NIST-certified FIPS crypto packages - fips-preview +.* +.* +.* - fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates - landscape +no +yes +no +Management and administration tool for Ubuntu - livepatch +yes +yes +yes +Canonical Livepatch service - realtime-kernel +no +yes +no +Ubuntu kernel with PREEMPT_RT patches integrated - ros +yes +yes +no +Security Updates for the Robot Operating System - ros-updates +yes +yes +no +All Updates for the Robot Operating System - """ - When I do a preflight check for `contract_token` formatted as json - Then stdout is a json matching the `ua_status` schema - When I do a preflight check for `contract_token` formatted as yaml - Then stdout is a yaml matching the `ua_status` schema - When I verify that a preflight check for `invalid_token` formatted as json exits 1 - Then stdout is a json matching the `ua_status` schema - And I will see the following on stdout: - """ - {"environment_vars": [], "errors": [{"message": "Invalid token. See https://ubuntu.com/pro/dashboard", "message_code": "attach-invalid-token", "service": null, "type": "system"}], "result": "failure", "services": [], "warnings": []} - """ - When I verify that a preflight check for `invalid_token` formatted as yaml exits 1 - Then stdout is a yaml matching the `ua_status` schema - And I will see the following on stdout: - """ - environment_vars: [] - errors: - - message: Invalid token. See https://ubuntu.com/pro/dashboard - message_code: attach-invalid-token - service: null - type: system - result: failure - services: [] - warnings: [] - """ - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - - @uses.config.contract_token - Scenario Outline: Simulate status in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I do a preflight check for `contract_token` without the all flag - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION - anbox-cloud +yes +.* - esm-apps +yes +yes +yes +Expanded Security Maintenance for Applications - esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure - fips +yes +yes +no +NIST-certified FIPS crypto packages - fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates - livepatch +yes +yes +yes +Canonical Livepatch service - ros +yes +yes +no +Security Updates for the Robot Operating System - usg +yes +yes +no +Security compliance and audit tools - """ - When I do a preflight check for `contract_token` with the all flag - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION - anbox-cloud +yes +.* - cc-eal +no +yes +no +Common Criteria EAL2 Provisioning Packages - esm-apps +yes +yes +yes +Expanded Security Maintenance for Applications - esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure - fips +yes +yes +no +NIST-certified FIPS crypto packages - fips-preview +no +yes +no +.* - fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates - landscape +no +yes +no +Management and administration tool for Ubuntu - livepatch +yes +yes +yes +Canonical Livepatch service - realtime-kernel +no +yes +no +Ubuntu kernel with PREEMPT_RT patches integrated - ros +yes +yes +no +Security Updates for the Robot Operating System - ros-updates +no +yes +no +All Updates for the Robot Operating System - usg +yes +yes +no +Security compliance and audit tools - """ - When I do a preflight check for `contract_token` formatted as json - Then stdout is a json matching the `ua_status` schema - When I do a preflight check for `contract_token` formatted as yaml - Then stdout is a yaml matching the `ua_status` schema - When I verify that a preflight check for `invalid_token` formatted as json exits 1 - Then stdout is a json matching the `ua_status` schema - And I will see the following on stdout: - """ - {"environment_vars": [], "errors": [{"message": "Invalid token. See https://ubuntu.com/pro/dashboard", "message_code": "attach-invalid-token", "service": null, "type": "system"}], "result": "failure", "services": [], "warnings": []} - """ - When I verify that a preflight check for `invalid_token` formatted as yaml exits 1 - Then stdout is a yaml matching the `ua_status` schema - And I will see the following on stdout: - """ - environment_vars: [] - errors: - - message: Invalid token. See https://ubuntu.com/pro/dashboard - message_code: attach-invalid-token - service: null - type: system - result: failure - services: [] - warnings: [] - """ - - Examples: ubuntu release - | release | machine_type | - | focal | lxd-container | - - @uses.config.contract_token - Scenario Outline: Simulate status in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I do a preflight check for `contract_token` without the all flag - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION - anbox-cloud +yes +.* - esm-apps +yes +yes +yes +Expanded Security Maintenance for Applications - esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure - fips-preview +yes +yes +no +.* - fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates - livepatch +yes +yes +yes +Canonical Livepatch service - realtime-kernel +yes +yes +no +Ubuntu kernel with PREEMPT_RT patches integrated - usg +yes +yes +no +Security compliance and audit tools - """ - When I do a preflight check for `contract_token` with the all flag - Then stdout matches regexp: - """ - SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION - anbox-cloud +yes +.* - cc-eal +no +yes +no +Common Criteria EAL2 Provisioning Packages - esm-apps +yes +yes +yes +Expanded Security Maintenance for Applications - esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure - fips +no +yes +no +NIST-certified FIPS crypto packages - fips-preview +yes +yes +no +.* - fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates - landscape +no +yes +no +Management and administration tool for Ubuntu - livepatch +yes +yes +yes +Canonical Livepatch service - realtime-kernel +yes +yes +no +Ubuntu kernel with PREEMPT_RT patches integrated - ros +no +yes +no +Security Updates for the Robot Operating System - ros-updates +no +yes +no +All Updates for the Robot Operating System - usg +yes +yes +no +Security compliance and audit tools - """ - When I do a preflight check for `contract_token` formatted as json - Then stdout is a json matching the `ua_status` schema - When I do a preflight check for `contract_token` formatted as yaml - Then stdout is a yaml matching the `ua_status` schema - When I verify that a preflight check for `invalid_token` formatted as json exits 1 - Then stdout is a json matching the `ua_status` schema - And I will see the following on stdout: - """ - {"environment_vars": [], "errors": [{"message": "Invalid token. See https://ubuntu.com/pro/dashboard", "message_code": "attach-invalid-token", "service": null, "type": "system"}], "result": "failure", "services": [], "warnings": []} - """ - When I verify that a preflight check for `invalid_token` formatted as yaml exits 1 - Then stdout is a yaml matching the `ua_status` schema - And I will see the following on stdout: - """ - environment_vars: [] - errors: - - message: Invalid token. See https://ubuntu.com/pro/dashboard - message_code: attach-invalid-token - service: null - type: system - result: failure - services: [] - warnings: [] - """ - - Examples: ubuntu release - | release | machine_type | - | jammy | lxd-container | - - - @uses.config.contract_token_staging_expired - Scenario Outline: Simulate status with expired token in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `sed -i 's/contracts.can/contracts.staging.can/' /etc/ubuntu-advantage/uaclient.conf` with sudo - And I verify that a preflight check for `contract_token_staging_expired` formatted as json exits 1 - Then stdout is a json matching the `ua_status` schema - And stdout matches regexp: - """ - \"result\": \"failure\" - """ - And stdout matches regexp: - """ - \"message\": \"Attach denied:\\nContract .* expired on .*\" - """ - When I verify that a preflight check for `contract_token_staging_expired` formatted as yaml exits 1 - Then stdout is a yaml matching the `ua_status` schema - Then stdout matches regexp: - """ - errors: - - message: 'Attach denied: - - Contract .* expired on .* - """ - When I verify that a preflight check for `contract_token_staging_expired` without the all flag exits 1 - Then stdout matches regexp: - """ - This token is not valid. - Attach denied: - Contract \".*\" expired on .* - Visit https://ubuntu.com/pro/dashboard to manage contract tokens. - - SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION - (anbox-cloud +(yes|no) +.*)? - ?cc-eal +yes +yes +no +Common Criteria EAL2 Provisioning Packages - cis +yes +yes +no +Security compliance and audit tools - esm-apps +yes +no +no +Expanded Security Maintenance for Applications - esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure - fips +yes +yes +no +NIST-certified FIPS crypto packages - fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates - livepatch +yes +yes +yes +Canonical Livepatch service - ros +yes +no +no +Security Updates for the Robot Operating System - ros-updates +yes +no +no +All Updates for the Robot Operating System - """ - - Examples: ubuntu release - | release | machine_type | - | xenial | lxd-container | - | bionic | lxd-container | - - @uses.config.contract_token_staging_expired - Scenario Outline: Simulate status with expired token in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `sed -i 's/contracts.can/contracts.staging.can/' /etc/ubuntu-advantage/uaclient.conf` with sudo - And I verify that a preflight check for `contract_token_staging_expired` formatted as json exits 1 - Then stdout is a json matching the `ua_status` schema - And stdout matches regexp: - """ - \"result\": \"failure\" - """ - And stdout matches regexp: - """ - \"message\": \"Attach denied:\\nContract .* expired on .*\" - """ - When I verify that a preflight check for `contract_token_staging_expired` formatted as yaml exits 1 - Then stdout is a yaml matching the `ua_status` schema - Then stdout matches regexp: - """ - errors: - - message: 'Attach denied: - - Contract .* expired on .* - """ - When I verify that a preflight check for `contract_token_staging_expired` without the all flag exits 1 - Then stdout matches regexp: - """ - This token is not valid. - Attach denied: - Contract \".*\" expired on .* - Visit https://ubuntu.com/pro/dashboard to manage contract tokens. - - SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION - anbox-cloud +yes +.* - esm-apps +yes +no +no +Expanded Security Maintenance for Applications - esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure - fips +yes +yes +no +NIST-certified FIPS crypto packages - fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates - livepatch +yes +yes +yes +Canonical Livepatch service - ros +yes +no +no +Security Updates for the Robot Operating System - usg +yes +yes +no +Security compliance and audit tools - """ - - Examples: ubuntu release - | release | machine_type | - | focal | lxd-container | - - @uses.config.contract_token_staging_expired - Scenario Outline: Simulate status with expired token in a ubuntu machine - Given a `` `` machine with ubuntu-advantage-tools installed - When I run `sed -i 's/contracts.can/contracts.staging.can/' /etc/ubuntu-advantage/uaclient.conf` with sudo - And I verify that a preflight check for `contract_token_staging_expired` formatted as json exits 1 - Then stdout is a json matching the `ua_status` schema - And stdout matches regexp: - """ - \"result\": \"failure\" - """ - And stdout matches regexp: - """ - \"message\": \"Attach denied:\\nContract .* expired on .*\" - """ - When I verify that a preflight check for `contract_token_staging_expired` formatted as yaml exits 1 - Then stdout is a yaml matching the `ua_status` schema - Then stdout matches regexp: - """ - errors: - - message: 'Attach denied: - - Contract .* expired on .* - """ - When I verify that a preflight check for `contract_token_staging_expired` without the all flag exits 1 - Then stdout matches regexp: - """ - This token is not valid. - Attach denied: - Contract \".*\" expired on .* - Visit https://ubuntu.com/pro/dashboard to manage contract tokens. - - SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION - anbox-cloud +yes +.* - esm-apps +yes +no +no +Expanded Security Maintenance for Applications - esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure - fips +yes +yes +no +NIST-certified FIPS crypto packages - fips-preview +yes +yes +no +Preview of FIPS crypto packages undergoing certification with NIST - fips-updates +yes +yes +no +.* - livepatch +yes +yes +yes +Canonical Livepatch service - """ - - Examples: ubuntu release - | release | machine_type | - | jammy | lxd-container | + Scenario Outline: Unattached status in a ubuntu machine - formatted + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `pro status --format json` as non-root + Then stdout is a json matching the `ua_status` schema + When I run `pro status --format yaml` as non-root + Then stdout is a yaml matching the `ua_status` schema + When I run `sed -i 's/contracts.can/invalidurl.notcan/' /etc/ubuntu-advantage/uaclient.conf` with sudo + And I verify that running `pro status --format json` `as non-root` exits `1` + Then stdout is a json matching the `ua_status` schema + And stdout matches regexp: + """ + {"environment_vars": \[\], "errors": \[{"message": "Failed to connect to .*\\n\[Errno -2\] Name or service not known\\n", "message_code": "connectivity-error", "service": null, "type": "system"}\], "result": "failure", "services": \[\], "warnings": \[\]} + """ + And I verify that running `pro status --format yaml` `as non-root` exits `1` + Then stdout is a yaml matching the `ua_status` schema + And stdout matches regexp: + """ + environment_vars: \[\] + errors: + - message: 'Failed to connect to https://invalidurl.notcanonical.com/v1/resources(.*) + + \[Errno -2\] Name or service not known + + ' + message_code: connectivity-error + service: null + type: system + result: failure + services: \[\] + warnings: \[\] + """ + + Examples: ubuntu release + | release | machine_type | + | bionic | lxd-container | + | focal | lxd-container | + | xenial | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | + | noble | lxd-container | + + Scenario Outline: Unattached status in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +DESCRIPTION + (anbox-cloud +(yes|no) +.*)? + ?cc-eal +yes +Common Criteria EAL2 Provisioning Packages + cis +yes +Security compliance and audit tools + esm-apps +yes +Expanded Security Maintenance for Applications + esm-infra +yes +Expanded Security Maintenance for Infrastructure + fips +yes +NIST-certified FIPS crypto packages + fips-updates +yes +FIPS compliant crypto packages with stable security updates + livepatch +yes +(Canonical Livepatch service|Current kernel is not supported) + ros +yes +Security Updates for the Robot Operating System + ros-updates +yes +All Updates for the Robot Operating System + + For a list of all Ubuntu Pro services, run 'pro status --all' + + This machine is not attached to an Ubuntu Pro subscription. + See https://ubuntu.com/pro + """ + When I verify root and non-root `pro status --all` calls have the same output + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +DESCRIPTION + anbox-cloud +(yes|no) +.* + cc-eal +yes +Common Criteria EAL2 Provisioning Packages + cis +yes +Security compliance and audit tools + esm-apps +yes +Expanded Security Maintenance for Applications + esm-infra +yes +Expanded Security Maintenance for Infrastructure + fips +yes +NIST-certified FIPS crypto packages + fips-preview +no +.* + fips-updates +yes +FIPS compliant crypto packages with stable security updates + landscape +no +Management and administration tool for Ubuntu + livepatch +yes +(Canonical Livepatch service|Current kernel is not supported) + realtime-kernel +no +Ubuntu kernel with PREEMPT_RT patches integrated + ros +yes +Security Updates for the Robot Operating System + ros-updates +yes +All Updates for the Robot Operating System + + This machine is not attached to an Ubuntu Pro subscription. + See https://ubuntu.com/pro + """ + When I append the following on uaclient config: + """ + features: + allow_beta: true + """ + And I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +DESCRIPTION + (anbox-cloud +(yes|no) +.*)? + ?cc-eal +yes +Common Criteria EAL2 Provisioning Packages + cis +yes +Security compliance and audit tools + esm-apps +yes +Expanded Security Maintenance for Applications + esm-infra +yes +Expanded Security Maintenance for Infrastructure + fips +yes +NIST-certified FIPS crypto packages + fips-updates +yes +FIPS compliant crypto packages with stable security updates + livepatch +yes +(Canonical Livepatch service|Current kernel is not supported) + ros +yes +Security Updates for the Robot Operating System + ros-updates +yes +All Updates for the Robot Operating System + + FEATURES + allow_beta: True + + For a list of all Ubuntu Pro services, run 'pro status --all' + + This machine is not attached to an Ubuntu Pro subscription. + See https://ubuntu.com/pro + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + + Scenario Outline: Unattached status in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify root and non-root `pro status` calls have the same output + When I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +DESCRIPTION + anbox-cloud +yes +.* + esm-apps +yes +Expanded Security Maintenance for Applications + esm-infra +yes +Expanded Security Maintenance for Infrastructure + fips +yes +NIST-certified FIPS crypto packages + fips-updates +yes +FIPS compliant crypto packages with stable security updates + livepatch +yes +Canonical Livepatch service + ros +yes +Security Updates for the Robot Operating System + usg +yes +Security compliance and audit tools + + For a list of all Ubuntu Pro services, run 'pro status --all' + + This machine is not attached to an Ubuntu Pro subscription. + See https://ubuntu.com/pro + """ + When I verify root and non-root `pro status --all` calls have the same output + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +DESCRIPTION + anbox-cloud +yes +.* + cc-eal +no +Common Criteria EAL2 Provisioning Packages + esm-apps +yes +Expanded Security Maintenance for Applications + esm-infra +yes +Expanded Security Maintenance for Infrastructure + fips +yes +NIST-certified FIPS crypto packages + fips-preview +no +.* + fips-updates +yes +FIPS compliant crypto packages with stable security updates + landscape +no +Management and administration tool for Ubuntu + livepatch +yes +Canonical Livepatch service + realtime-kernel +no +Ubuntu kernel with PREEMPT_RT patches integrated + ros +yes +Security Updates for the Robot Operating System + ros-updates +no +All Updates for the Robot Operating System + usg +yes +Security compliance and audit tools + + This machine is not attached to an Ubuntu Pro subscription. + See https://ubuntu.com/pro + """ + When I append the following on uaclient config: + """ + features: + allow_beta: true + """ + When I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +DESCRIPTION + anbox-cloud +yes +.* + esm-apps +yes +Expanded Security Maintenance for Applications + esm-infra +yes +Expanded Security Maintenance for Infrastructure + fips +yes +NIST-certified FIPS crypto packages + fips-updates +yes +FIPS compliant crypto packages with stable security updates + livepatch +yes +Canonical Livepatch service + ros +yes +Security Updates for the Robot Operating System + usg +yes +Security compliance and audit tools + + FEATURES + allow_beta: True + + For a list of all Ubuntu Pro services, run 'pro status --all' + + This machine is not attached to an Ubuntu Pro subscription. + See https://ubuntu.com/pro + """ + + Examples: ubuntu release + | release | machine_type | + | focal | lxd-container | + + Scenario Outline: Unattached status in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +DESCRIPTION + anbox-cloud +yes +.* + esm-apps +yes +Expanded Security Maintenance for Applications + esm-infra +yes +Expanded Security Maintenance for Infrastructure + fips-preview +yes +.* + fips-updates +yes +FIPS compliant crypto packages with stable security updates + livepatch +yes +Canonical Livepatch service + realtime-kernel +yes +Ubuntu kernel with PREEMPT_RT patches integrated + usg +yes +Security compliance and audit tools + + For a list of all Ubuntu Pro services, run 'pro status --all' + + This machine is not attached to an Ubuntu Pro subscription. + See https://ubuntu.com/pro + """ + When I verify root and non-root `pro status --all` calls have the same output + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +DESCRIPTION + anbox-cloud +yes +.* + cc-eal +no +Common Criteria EAL2 Provisioning Packages + esm-apps +yes +Expanded Security Maintenance for Applications + esm-infra +yes +Expanded Security Maintenance for Infrastructure + fips +no +NIST-certified FIPS crypto packages + fips-preview +yes +.* + fips-updates +yes +FIPS compliant crypto packages with stable security updates + landscape +no +Management and administration tool for Ubuntu + livepatch +yes +Canonical Livepatch service + realtime-kernel +yes +Ubuntu kernel with PREEMPT_RT patches integrated + ros +no +Security Updates for the Robot Operating System + ros-updates +no +All Updates for the Robot Operating System + usg +yes +Security compliance and audit tools + + This machine is not attached to an Ubuntu Pro subscription. + See https://ubuntu.com/pro + """ + When I append the following on uaclient config: + """ + features: + allow_beta: true + """ + When I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +DESCRIPTION + anbox-cloud +yes +.* + esm-apps +yes +Expanded Security Maintenance for Applications + esm-infra +yes +Expanded Security Maintenance for Infrastructure + fips-preview +yes +.* + fips-updates +yes +FIPS compliant crypto packages with stable security updates + livepatch +yes +Canonical Livepatch service + realtime-kernel +yes +Ubuntu kernel with PREEMPT_RT patches integrated + usg +yes +Security compliance and audit tools + + FEATURES + allow_beta: True + + For a list of all Ubuntu Pro services, run 'pro status --all' + + This machine is not attached to an Ubuntu Pro subscription. + See https://ubuntu.com/pro + """ + + Examples: ubuntu release + | release | machine_type | + | jammy | lxd-container | + + Scenario Outline: Unattached status in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +DESCRIPTION + anbox-cloud +yes +.* + esm-apps +yes +Expanded Security Maintenance for Applications + esm-infra +yes +Expanded Security Maintenance for Infrastructure + landscape +yes +Management and administration tool for Ubuntu + livepatch +yes +Canonical Livepatch service + + For a list of all Ubuntu Pro services, run 'pro status --all' + + This machine is not attached to an Ubuntu Pro subscription. + See https://ubuntu.com/pro + """ + When I verify root and non-root `pro status --all` calls have the same output + And I run `pro status --all` as non-root + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +DESCRIPTION + anbox-cloud +yes +.* + cc-eal +no +Common Criteria EAL2 Provisioning Packages + esm-apps +yes +Expanded Security Maintenance for Applications + esm-infra +yes +Expanded Security Maintenance for Infrastructure + fips +no +NIST-certified FIPS crypto packages + fips-preview +no +.* + fips-updates +no +FIPS compliant crypto packages with stable security updates + landscape +yes +Management and administration tool for Ubuntu + livepatch +yes +Canonical Livepatch service + realtime-kernel +no +Ubuntu kernel with PREEMPT_RT patches integrated + ros +no +Security Updates for the Robot Operating System + ros-updates +no +All Updates for the Robot Operating System + usg +no +Security compliance and audit tools + + This machine is not attached to an Ubuntu Pro subscription. + See https://ubuntu.com/pro + """ + When I append the following on uaclient config: + """ + features: + allow_beta: true + """ + When I verify root and non-root `pro status` calls have the same output + And I run `pro status` as non-root + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +DESCRIPTION + anbox-cloud +yes +.* + esm-apps +yes +Expanded Security Maintenance for Applications + esm-infra +yes +Expanded Security Maintenance for Infrastructure + landscape +yes +Management and administration tool for Ubuntu + livepatch +yes +Canonical Livepatch service + + FEATURES + allow_beta: True + + For a list of all Ubuntu Pro services, run 'pro status --all' + + This machine is not attached to an Ubuntu Pro subscription. + See https://ubuntu.com/pro + """ + + Examples: ubuntu release + | release | machine_type | + | noble | lxd-container | + + @uses.config.contract_token + Scenario Outline: Simulate status in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I do a preflight check for `contract_token` without the all flag + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION + (anbox-cloud +yes +.*)? + ?cc-eal +yes +yes +no +Common Criteria EAL2 Provisioning Packages + cis +yes +yes +no +Security compliance and audit tools + esm-apps +yes +yes +yes +Expanded Security Maintenance for Applications + esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure + fips +yes +yes +no +NIST-certified FIPS crypto packages + fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates + livepatch +yes +yes +yes +Canonical Livepatch service + ros +yes +yes +no +Security Updates for the Robot Operating System + ros-updates +yes +yes +no +All Updates for the Robot Operating System + """ + When I do a preflight check for `contract_token` with the all flag + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION + anbox-cloud +(yes|no) +.* + cc-eal +yes +yes +no +Common Criteria EAL2 Provisioning Packages + cis +yes +yes +no +Security compliance and audit tools + esm-apps +yes +yes +yes +Expanded Security Maintenance for Applications + esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure + fips +yes +yes +no +NIST-certified FIPS crypto packages + fips-preview +.* +.* +.* + fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates + landscape +no +yes +no +Management and administration tool for Ubuntu + livepatch +yes +yes +yes +Canonical Livepatch service + realtime-kernel +no +yes +no +Ubuntu kernel with PREEMPT_RT patches integrated + ros +yes +yes +no +Security Updates for the Robot Operating System + ros-updates +yes +yes +no +All Updates for the Robot Operating System + """ + When I do a preflight check for `contract_token` formatted as json + Then stdout is a json matching the `ua_status` schema + When I do a preflight check for `contract_token` formatted as yaml + Then stdout is a yaml matching the `ua_status` schema + When I verify that a preflight check for `invalid_token` formatted as json exits 1 + Then stdout is a json matching the `ua_status` schema + And I will see the following on stdout: + """ + {"environment_vars": [], "errors": [{"message": "Invalid token. See https://ubuntu.com/pro/dashboard", "message_code": "attach-invalid-token", "service": null, "type": "system"}], "result": "failure", "services": [], "warnings": []} + """ + When I verify that a preflight check for `invalid_token` formatted as yaml exits 1 + Then stdout is a yaml matching the `ua_status` schema + And I will see the following on stdout: + """ + environment_vars: [] + errors: + - message: Invalid token. See https://ubuntu.com/pro/dashboard + message_code: attach-invalid-token + service: null + type: system + result: failure + services: [] + warnings: [] + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + + @uses.config.contract_token + Scenario Outline: Simulate status in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I do a preflight check for `contract_token` without the all flag + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION + anbox-cloud +yes +.* + esm-apps +yes +yes +yes +Expanded Security Maintenance for Applications + esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure + fips +yes +yes +no +NIST-certified FIPS crypto packages + fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates + livepatch +yes +yes +yes +Canonical Livepatch service + ros +yes +yes +no +Security Updates for the Robot Operating System + usg +yes +yes +no +Security compliance and audit tools + """ + When I do a preflight check for `contract_token` with the all flag + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION + anbox-cloud +yes +.* + cc-eal +no +yes +no +Common Criteria EAL2 Provisioning Packages + esm-apps +yes +yes +yes +Expanded Security Maintenance for Applications + esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure + fips +yes +yes +no +NIST-certified FIPS crypto packages + fips-preview +no +yes +no +.* + fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates + landscape +no +yes +no +Management and administration tool for Ubuntu + livepatch +yes +yes +yes +Canonical Livepatch service + realtime-kernel +no +yes +no +Ubuntu kernel with PREEMPT_RT patches integrated + ros +yes +yes +no +Security Updates for the Robot Operating System + ros-updates +no +yes +no +All Updates for the Robot Operating System + usg +yes +yes +no +Security compliance and audit tools + """ + When I do a preflight check for `contract_token` formatted as json + Then stdout is a json matching the `ua_status` schema + When I do a preflight check for `contract_token` formatted as yaml + Then stdout is a yaml matching the `ua_status` schema + When I verify that a preflight check for `invalid_token` formatted as json exits 1 + Then stdout is a json matching the `ua_status` schema + And I will see the following on stdout: + """ + {"environment_vars": [], "errors": [{"message": "Invalid token. See https://ubuntu.com/pro/dashboard", "message_code": "attach-invalid-token", "service": null, "type": "system"}], "result": "failure", "services": [], "warnings": []} + """ + When I verify that a preflight check for `invalid_token` formatted as yaml exits 1 + Then stdout is a yaml matching the `ua_status` schema + And I will see the following on stdout: + """ + environment_vars: [] + errors: + - message: Invalid token. See https://ubuntu.com/pro/dashboard + message_code: attach-invalid-token + service: null + type: system + result: failure + services: [] + warnings: [] + """ + + Examples: ubuntu release + | release | machine_type | + | focal | lxd-container | + + @uses.config.contract_token + Scenario Outline: Simulate status in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I do a preflight check for `contract_token` without the all flag + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION + anbox-cloud +yes +.* + esm-apps +yes +yes +yes +Expanded Security Maintenance for Applications + esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure + fips-preview +yes +yes +no +.* + fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates + livepatch +yes +yes +yes +Canonical Livepatch service + realtime-kernel +yes +yes +no +Ubuntu kernel with PREEMPT_RT patches integrated + usg +yes +yes +no +Security compliance and audit tools + """ + When I do a preflight check for `contract_token` with the all flag + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION + anbox-cloud +yes +.* + cc-eal +no +yes +no +Common Criteria EAL2 Provisioning Packages + esm-apps +yes +yes +yes +Expanded Security Maintenance for Applications + esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure + fips +no +yes +no +NIST-certified FIPS crypto packages + fips-preview +yes +yes +no +.* + fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates + landscape +no +yes +no +Management and administration tool for Ubuntu + livepatch +yes +yes +yes +Canonical Livepatch service + realtime-kernel +yes +yes +no +Ubuntu kernel with PREEMPT_RT patches integrated + ros +no +yes +no +Security Updates for the Robot Operating System + ros-updates +no +yes +no +All Updates for the Robot Operating System + usg +yes +yes +no +Security compliance and audit tools + """ + When I do a preflight check for `contract_token` formatted as json + Then stdout is a json matching the `ua_status` schema + When I do a preflight check for `contract_token` formatted as yaml + Then stdout is a yaml matching the `ua_status` schema + When I verify that a preflight check for `invalid_token` formatted as json exits 1 + Then stdout is a json matching the `ua_status` schema + And I will see the following on stdout: + """ + {"environment_vars": [], "errors": [{"message": "Invalid token. See https://ubuntu.com/pro/dashboard", "message_code": "attach-invalid-token", "service": null, "type": "system"}], "result": "failure", "services": [], "warnings": []} + """ + When I verify that a preflight check for `invalid_token` formatted as yaml exits 1 + Then stdout is a yaml matching the `ua_status` schema + And I will see the following on stdout: + """ + environment_vars: [] + errors: + - message: Invalid token. See https://ubuntu.com/pro/dashboard + message_code: attach-invalid-token + service: null + type: system + result: failure + services: [] + warnings: [] + """ + + Examples: ubuntu release + | release | machine_type | + | jammy | lxd-container | + + @uses.config.contract_token + Scenario Outline: Simulate status in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I do a preflight check for `contract_token` without the all flag + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION + anbox-cloud +yes +.* + esm-apps +yes +yes +yes +Expanded Security Maintenance for Applications + esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure + landscape +yes +yes +no +Management and administration tool for Ubuntu + livepatch +yes +yes +yes +Canonical Livepatch service + """ + When I do a preflight check for `contract_token` with the all flag + Then stdout matches regexp: + """ + SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION + anbox-cloud +yes +.* + cc-eal +no +yes +no +Common Criteria EAL2 Provisioning Packages + esm-apps +yes +yes +yes +Expanded Security Maintenance for Applications + esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure + fips +no +yes +no +NIST-certified FIPS crypto packages + fips-preview +no +yes +no +.* + fips-updates +no +yes +no +FIPS compliant crypto packages with stable security updates + landscape +yes +yes +no +Management and administration tool for Ubuntu + livepatch +yes +yes +yes +Canonical Livepatch service + realtime-kernel +no +yes +no +Ubuntu kernel with PREEMPT_RT patches integrated + ros +no +yes +no +Security Updates for the Robot Operating System + ros-updates +no +yes +no +All Updates for the Robot Operating System + usg +no +yes +no +Security compliance and audit tools + """ + When I do a preflight check for `contract_token` formatted as json + Then stdout is a json matching the `ua_status` schema + When I do a preflight check for `contract_token` formatted as yaml + Then stdout is a yaml matching the `ua_status` schema + When I verify that a preflight check for `invalid_token` formatted as json exits 1 + Then stdout is a json matching the `ua_status` schema + And I will see the following on stdout: + """ + {"environment_vars": [], "errors": [{"message": "Invalid token. See https://ubuntu.com/pro/dashboard", "message_code": "attach-invalid-token", "service": null, "type": "system"}], "result": "failure", "services": [], "warnings": []} + """ + When I verify that a preflight check for `invalid_token` formatted as yaml exits 1 + Then stdout is a yaml matching the `ua_status` schema + And I will see the following on stdout: + """ + environment_vars: [] + errors: + - message: Invalid token. See https://ubuntu.com/pro/dashboard + message_code: attach-invalid-token + service: null + type: system + result: failure + services: [] + warnings: [] + """ + + Examples: ubuntu release + | release | machine_type | + | noble | lxd-container | + + @uses.config.contract_token_staging_expired + Scenario Outline: Simulate status with expired token in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `sed -i 's/contracts.can/contracts.staging.can/' /etc/ubuntu-advantage/uaclient.conf` with sudo + And I verify that a preflight check for `contract_token_staging_expired` formatted as json exits 1 + Then stdout is a json matching the `ua_status` schema + And stdout matches regexp: + """ + \"result\": \"failure\" + """ + And stdout matches regexp: + """ + \"message\": \"Attach denied:\\nContract .* expired on .*\" + """ + When I verify that a preflight check for `contract_token_staging_expired` formatted as yaml exits 1 + Then stdout is a yaml matching the `ua_status` schema + Then stdout matches regexp: + """ + errors: + - message: 'Attach denied: + + Contract .* expired on .* + """ + When I verify that a preflight check for `contract_token_staging_expired` without the all flag exits 1 + Then stdout matches regexp: + """ + This token is not valid. + Attach denied: + Contract \".*\" expired on .* + Visit https://ubuntu.com/pro/dashboard to manage contract tokens. + + SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION + (anbox-cloud +(yes|no) +.*)? + ?cc-eal +yes +yes +no +Common Criteria EAL2 Provisioning Packages + cis +yes +yes +no +Security compliance and audit tools + esm-apps +yes +no +no +Expanded Security Maintenance for Applications + esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure + fips +yes +yes +no +NIST-certified FIPS crypto packages + fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates + livepatch +yes +yes +yes +Canonical Livepatch service + ros +yes +no +no +Security Updates for the Robot Operating System + ros-updates +yes +no +no +All Updates for the Robot Operating System + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + + @uses.config.contract_token_staging_expired + Scenario Outline: Simulate status with expired token in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `sed -i 's/contracts.can/contracts.staging.can/' /etc/ubuntu-advantage/uaclient.conf` with sudo + And I verify that a preflight check for `contract_token_staging_expired` formatted as json exits 1 + Then stdout is a json matching the `ua_status` schema + And stdout matches regexp: + """ + \"result\": \"failure\" + """ + And stdout matches regexp: + """ + \"message\": \"Attach denied:\\nContract .* expired on .*\" + """ + When I verify that a preflight check for `contract_token_staging_expired` formatted as yaml exits 1 + Then stdout is a yaml matching the `ua_status` schema + Then stdout matches regexp: + """ + errors: + - message: 'Attach denied: + + Contract .* expired on .* + """ + When I verify that a preflight check for `contract_token_staging_expired` without the all flag exits 1 + Then stdout matches regexp: + """ + This token is not valid. + Attach denied: + Contract \".*\" expired on .* + Visit https://ubuntu.com/pro/dashboard to manage contract tokens. + + SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION + anbox-cloud +yes +.* + esm-apps +yes +no +no +Expanded Security Maintenance for Applications + esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure + fips +yes +yes +no +NIST-certified FIPS crypto packages + fips-updates +yes +yes +no +FIPS compliant crypto packages with stable security updates + livepatch +yes +yes +yes +Canonical Livepatch service + ros +yes +no +no +Security Updates for the Robot Operating System + usg +yes +yes +no +Security compliance and audit tools + """ + + Examples: ubuntu release + | release | machine_type | + | focal | lxd-container | + + @uses.config.contract_token_staging_expired + Scenario Outline: Simulate status with expired token in a ubuntu machine + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `sed -i 's/contracts.can/contracts.staging.can/' /etc/ubuntu-advantage/uaclient.conf` with sudo + And I verify that a preflight check for `contract_token_staging_expired` formatted as json exits 1 + Then stdout is a json matching the `ua_status` schema + And stdout matches regexp: + """ + \"result\": \"failure\" + """ + And stdout matches regexp: + """ + \"message\": \"Attach denied:\\nContract .* expired on .*\" + """ + When I verify that a preflight check for `contract_token_staging_expired` formatted as yaml exits 1 + Then stdout is a yaml matching the `ua_status` schema + Then stdout matches regexp: + """ + errors: + - message: 'Attach denied: + + Contract .* expired on .* + """ + When I verify that a preflight check for `contract_token_staging_expired` without the all flag exits 1 + Then stdout matches regexp: + """ + This token is not valid. + Attach denied: + Contract \".*\" expired on .* + Visit https://ubuntu.com/pro/dashboard to manage contract tokens. + + SERVICE +AVAILABLE +ENTITLED +AUTO_ENABLED +DESCRIPTION + anbox-cloud +yes +.* + esm-apps +yes +no +no +Expanded Security Maintenance for Applications + esm-infra +yes +yes +yes +Expanded Security Maintenance for Infrastructure + fips +yes +yes +no +NIST-certified FIPS crypto packages + fips-preview +yes +yes +no +Preview of FIPS crypto packages undergoing certification with NIST + fips-updates +yes +yes +no +.* + livepatch +yes +yes +yes +Canonical Livepatch service + """ + + Examples: ubuntu release + | release | machine_type | + | jammy | lxd-container | + + Scenario Outline: Check notice file read permission + Given a `` `` machine with ubuntu-advantage-tools installed + When I run `mkdir -p /run/ubuntu-advantage/notices` with sudo + When I run `touch /run/ubuntu-advantage/notices/crasher` with sudo + When I run `chmod 0 /run/ubuntu-advantage/notices/crasher` with sudo + When I run `mkdir -p /var/lib/ubuntu-advantage/notices` with sudo + When I run `touch /var/lib/ubuntu-advantage/notices/crasher` with sudo + When I run `chmod 0 /var/lib/ubuntu-advantage/notices/crasher` with sudo + When I run `touch /run/ubuntu-advantage/notices/10-reboot_required` with sudo + When I run `pro status` as non-root + Then stdout matches regexp: + """ + NOTICES + System reboot required + """ + + Examples: ubuntu release + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + | focal | lxd-container | + | jammy | lxd-container | + | mantic | lxd-container | diff -Nru ubuntu-advantage-tools-31.2.3~16.04/integration-requirements.txt ubuntu-advantage-tools-32~16.04/integration-requirements.txt --- ubuntu-advantage-tools-31.2.3~16.04/integration-requirements.txt 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/integration-requirements.txt 2024-04-23 13:37:02.000000000 +0000 @@ -2,7 +2,8 @@ behave jsonschema PyHamcrest -pycloudlib==1!5.6.0 +pycloudlib==1!6.5.0 +requests toml==0.10 ipdb diff -Nru ubuntu-advantage-tools-31.2.3~16.04/lib/apt_news.py ubuntu-advantage-tools-32~16.04/lib/apt_news.py --- ubuntu-advantage-tools-31.2.3~16.04/lib/apt_news.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/lib/apt_news.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,12 +1,10 @@ #!/usr/bin/python3 -import logging from datetime import datetime, timedelta, timezone -from uaclient import apt, defaults +from uaclient import apt, log from uaclient.apt_news import update_apt_news from uaclient.config import UAConfig -from uaclient.daemon import setup_logging def main(cfg: UAConfig): @@ -22,15 +20,6 @@ if __name__ == "__main__": - setup_logging( - logging.INFO, - logging.DEBUG, - defaults.CONFIG_DEFAULTS["log_file"], - ) + log.setup_journald_logging() cfg = UAConfig() - setup_logging( - logging.INFO, - logging.DEBUG, - cfg.log_file, - ) main(cfg) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/lib/auto_attach.py ubuntu-advantage-tools-32~16.04/lib/auto_attach.py --- ubuntu-advantage-tools-31.2.3~16.04/lib/auto_attach.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/lib/auto_attach.py 2024-04-23 13:37:02.000000000 +0000 @@ -13,7 +13,7 @@ import logging import sys -from uaclient import defaults, http, messages, system +from uaclient import http, log, messages, system from uaclient.api.exceptions import ( AlreadyAttachedError, AutoAttachDisabledError, @@ -24,15 +24,16 @@ full_auto_attach, ) from uaclient.config import UAConfig -from uaclient.daemon import ( - AUTO_ATTACH_STATUS_MOTD_FILE, - retry_auto_attach, - setup_logging, -) +from uaclient.daemon import AUTO_ATTACH_STATUS_MOTD_FILE, retry_auto_attach from uaclient.files import state_files LOG = logging.getLogger("ubuntupro.lib.auto_attach") +# All known cloud-config keys which provide ubuntu pro configuration directives +CLOUD_INIT_UA_KEYS = set( + ["ubuntu-advantage", "ubuntu_advantage", "ubuntu_pro"] +) + try: import cloudinit.stages as ci_stages # type: ignore except ImportError: @@ -54,10 +55,7 @@ if init is None: return False - if init.cfg and ( - "ubuntu_advantage" in init.cfg.keys() - or "ubuntu-advantage" in init.cfg.keys() - ): + if init.cfg and CLOUD_INIT_UA_KEYS.intersection(init.cfg): return True return False @@ -103,16 +101,7 @@ if __name__ == "__main__": - setup_logging( - logging.INFO, - logging.DEBUG, - defaults.CONFIG_DEFAULTS["log_file"], - ) + log.setup_journald_logging() cfg = UAConfig() - setup_logging( - logging.INFO, - logging.DEBUG, - cfg.log_file, - ) http.configure_web_proxy(cfg.http_proxy, cfg.https_proxy) sys.exit(main(cfg)) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/lib/convert_list_to_deb822.py ubuntu-advantage-tools-32~16.04/lib/convert_list_to_deb822.py --- ubuntu-advantage-tools-31.2.3~16.04/lib/convert_list_to_deb822.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/lib/convert_list_to_deb822.py 2024-04-23 13:37:02.000000000 +0000 @@ -11,10 +11,10 @@ from aptsources.sourceslist import SourceEntry # type: ignore -from uaclient import entitlements +from uaclient import defaults, entitlements from uaclient.apt import _get_sources_file_content -from uaclient.cli import setup_logging from uaclient.config import UAConfig +from uaclient.log import setup_cli_logging from uaclient.system import ( ensure_file_absent, get_release_info, @@ -28,7 +28,7 @@ if series != "noble": sys.exit(0) - setup_logging(logging.DEBUG) + setup_cli_logging(logging.DEBUG, defaults.CONFIG_DEFAULTS["log_file"]) cfg = UAConfig() for entitlement_class in entitlements.ENTITLEMENT_CLASSES: diff -Nru ubuntu-advantage-tools-31.2.3~16.04/lib/daemon.py ubuntu-advantage-tools-32~16.04/lib/daemon.py --- ubuntu-advantage-tools-31.2.3~16.04/lib/daemon.py 2024-04-05 13:08:47.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/lib/daemon.py 2024-04-23 13:37:02.000000000 +0000 @@ -3,10 +3,9 @@ import sys import time -from uaclient import http, system +from uaclient import http, log, system from uaclient.config import UAConfig from uaclient.daemon import poll_for_pro_license, retry_auto_attach -from uaclient.log import setup_journald_logging LOG = logging.getLogger("ubuntupro.daemon") @@ -47,16 +46,13 @@ def main() -> int: - setup_journald_logging(logging.DEBUG, LOG) - # Make sure the ubuntupro.daemon logger does not generate double logging - LOG.propagate = False - setup_journald_logging(logging.ERROR, logging.getLogger("ubuntupro")) + log.setup_journald_logging() cfg = UAConfig() http.configure_web_proxy(cfg.http_proxy, cfg.https_proxy) - LOG.debug("daemon starting") + LOG.info("daemon starting") _wait_for_cloud_config() @@ -77,7 +73,7 @@ LOG.info("mode: retry auto attach") retry_auto_attach.retry_auto_attach(cfg) - LOG.debug("daemon ending") + LOG.info("daemon ending") return 0 diff -Nru ubuntu-advantage-tools-31.2.3~16.04/lib/esm_cache.py ubuntu-advantage-tools-32~16.04/lib/esm_cache.py --- ubuntu-advantage-tools-31.2.3~16.04/lib/esm_cache.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/lib/esm_cache.py 2024-04-23 13:37:02.000000000 +0000 @@ -2,10 +2,9 @@ import logging -from uaclient import defaults +from uaclient import log from uaclient.apt import update_esm_caches from uaclient.config import UAConfig -from uaclient.daemon import setup_logging LOG = logging.getLogger("ubuntupro.lib.esm_cache") @@ -19,15 +18,6 @@ if __name__ == "__main__": - setup_logging( - logging.INFO, - logging.DEBUG, - defaults.CONFIG_DEFAULTS["log_file"], - ) + log.setup_journald_logging() cfg = UAConfig() - setup_logging( - logging.INFO, - logging.DEBUG, - cfg.log_file, - ) main(cfg) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/lib/patch_status_json.py ubuntu-advantage-tools-32~16.04/lib/patch_status_json.py --- ubuntu-advantage-tools-31.2.3~16.04/lib/patch_status_json.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/lib/patch_status_json.py 2024-04-23 13:37:02.000000000 +0000 @@ -18,8 +18,7 @@ import json import logging -from uaclient import system, util -from uaclient.cli import setup_logging +from uaclient import config, defaults, log, system, util LOG = logging.getLogger("ubuntupro.lib.patch_status_json") @@ -64,7 +63,9 @@ if __name__ == "__main__": - setup_logging(logging.DEBUG) + log.setup_cli_logging(logging.DEBUG, defaults.CONFIG_DEFAULTS["log_level"]) + cfg = config.UAConfig() + log.setup_cli_logging(cfg.log_level, cfg.log_file) patch_status_json_schema_0_1( status_file="/var/lib/ubuntu-advantage/status.json" ) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/lib/postinst-migrations.sh ubuntu-advantage-tools-32~16.04/lib/postinst-migrations.sh --- ubuntu-advantage-tools-31.2.3~16.04/lib/postinst-migrations.sh 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/lib/postinst-migrations.sh 2024-05-10 17:07:05.000000000 +0000 @@ -0,0 +1,74 @@ +#!/bin/sh +# +# Migrations from previous ubuntu-pro-client/ubuntu-advantage-tools versions +# +# These exist in this script separate from the *.postinst scripts because they +# may need to be called from either ubuntu-pro-client.postinst or from +# ubuntu-advantage-tools.postinst, depending on the situation. +# +# Because ubuntu-advantage-tools Depends on ubuntu-pro-client, +# ubuntu-pro-client.postinst will always be executed before +# ubuntu-advantage-tools.postinst. +# +# If the system already has ubuntu-pro-client and is upgrading to a new version +# of ubuntu-pro-client, that means all migrations present in +# ubuntu-advantage-tools.postinst must have already run at the time the system +# upgraded to the renamed ubuntu-pro-client. That means the migrations in this +# file can and should execute as part of ubuntu-pro-client.postinst. +# +# If upgrading from before the rename to the current version (from before +# version 31), then not necessarily all migrations inside of +# ubuntu-advantage-tools.postinst will have already run. Because the migrations +# present in this file may depend on those previous migrations, then this file +# must execute at the end of ubuntu-advantage-tools.postinst. +# +# In practice, this file is executed conditionally in either +# ubuntu-pro-client.postinst or ubuntu-advantage-tools.postinst using version +# checks. If we're upgrading from a version earlier than 31, then this executes +# in ubuntu-advantage-tools.postinst. If we're upgrading from version 31 or +# later, then this executes in ubuntu-pro-client.postinst. +# +# +# +# Migrations should always be version-gated using PREVIOUS_PKG_VER and execute +# in order from oldest to newest. +# +# For example: +# if dpkg --compare-versions "$PREVIOUS_PKG_VER" lt "33~"; then +# # do the migrations to version 33 +# fi +# if dpkg --compare-versions "$PREVIOUS_PKG_VER" lt "34~"; then +# # do the migrations to version 34 +# fi +# + +set -e + +PREVIOUS_PKG_VER=$1 + +if dpkg --compare-versions "$PREVIOUS_PKG_VER" lt "32~"; then + # When we perform the write operation through + # UserConfigFile we write the public + # version of the user-config file with all the + # sensitive data removed. + # We also move the user-config.json file to the private + # directory + source_file="/var/lib/ubuntu-advantage/user-config.json" + destination_dir="/var/lib/ubuntu-advantage/private" + # Check if the source file exists + if [ -f "$source_file" ]; then + mkdir -p "$destination_dir" + # Move the user-config.json file to the private directory + mv "$source_file" "$destination_dir/user-config.json" + + /usr/bin/python3 -c " +from uaclient.files import UserConfigFileObject +try: + user_config_file = UserConfigFileObject() + content = user_config_file.read() + user_config_file.write(content) +except Exception as e: + print('Error while creating public user-config file: {}'.format(e)) +" + fi +fi diff -Nru ubuntu-advantage-tools-31.2.3~16.04/lib/reboot_cmds.py ubuntu-advantage-tools-32~16.04/lib/reboot_cmds.py --- ubuntu-advantage-tools-31.2.3~16.04/lib/reboot_cmds.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/lib/reboot_cmds.py 2024-04-23 13:37:02.000000000 +0000 @@ -18,16 +18,16 @@ import sys from uaclient import ( + api, config, contract, - defaults, exceptions, http, lock, + log, upgrade_lts_contract, ) from uaclient.api.u.pro.status.is_attached.v1 import _is_attached -from uaclient.cli import setup_logging from uaclient.entitlements.fips import FIPSEntitlement from uaclient.files import notices, state_files @@ -35,7 +35,7 @@ def fix_pro_pkg_holds(cfg: config.UAConfig): - status_cache = cfg.read_cache("status-cache") + status_cache = state_files.status_cache_file.read() if not status_cache: return for service in status_cache.get("services", []): @@ -47,17 +47,21 @@ # fips was not enabled, don't do anything return - LOG.debug("Attempting to remove Ubuntu Pro FIPS package holds") + LOG.info("Attempting to remove Ubuntu Pro FIPS package holds") fips = FIPSEntitlement(cfg) try: - fips.setup_apt_config() # Removes package holds - LOG.debug("Successfully removed Ubuntu Pro FIPS package holds") + fips.setup_apt_config( + progress=api.ProgressWrapper() + ) # Removes package holds + LOG.info("Successfully removed Ubuntu Pro FIPS package holds") except Exception as e: LOG.error(e) LOG.warning("Could not remove Ubuntu Pro FIPS package holds") try: - fips.install_packages(cleanup_on_failure=False) + fips.install_packages( + progress=api.ProgressWrapper(), cleanup_on_failure=False + ) except exceptions.UbuntuProError: LOG.warning("Failed to install packages at boot: %r", fips.packages) raise @@ -73,19 +77,19 @@ def main(cfg: config.UAConfig) -> int: if not state_files.reboot_cmd_marker_file.is_present: - LOG.debug("Skipping reboot_cmds. Marker file not present") + LOG.info("Skipping reboot_cmds. Marker file not present") notices.remove(notices.Notice.REBOOT_SCRIPT_FAILED) return 0 if not _is_attached(cfg).is_attached: - LOG.debug("Skipping reboot_cmds. Machine is unattached") + LOG.info("Skipping reboot_cmds. Machine is unattached") state_files.reboot_cmd_marker_file.delete() notices.remove(notices.Notice.REBOOT_SCRIPT_FAILED) return 0 - LOG.debug("Running reboot commands...") + LOG.info("Running reboot commands...") try: - with lock.RetryLock(cfg=cfg, lock_holder="pro-reboot-cmds"): + with lock.RetryLock(lock_holder="pro-reboot-cmds"): fix_pro_pkg_holds(cfg) refresh_contract(cfg) upgrade_lts_contract.process_contract_delta_after_apt_lock(cfg) @@ -108,16 +112,12 @@ notices.add(notices.Notice.REBOOT_SCRIPT_FAILED) return 1 - LOG.debug("Successfully ran all commands on reboot.") + LOG.info("Successfully ran all commands on reboot.") return 0 if __name__ == "__main__": - setup_logging( - logging.DEBUG, - defaults.CONFIG_DEFAULTS["log_file"], - ) + log.setup_journald_logging() cfg = config.UAConfig() - setup_logging(logging.DEBUG, log_file=cfg.log_file) http.configure_web_proxy(cfg.http_proxy, cfg.https_proxy) sys.exit(main(cfg=cfg)) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/lib/timer.py ubuntu-advantage-tools-32~16.04/lib/timer.py --- ubuntu-advantage-tools-31.2.3~16.04/lib/timer.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/lib/timer.py 2024-04-23 13:37:02.000000000 +0000 @@ -6,7 +6,7 @@ from datetime import datetime, timedelta, timezone from typing import Callable, Optional -from uaclient import http +from uaclient import http, log from uaclient.config import UAConfig from uaclient.exceptions import InvalidFileFormatError from uaclient.files.state_files import ( @@ -14,7 +14,6 @@ TimerJobState, timer_jobs_state_file, ) -from uaclient.log import setup_journald_logging from uaclient.timer.metering import metering_enabled_resources from uaclient.timer.update_contract_info import update_contract_info from uaclient.timer.update_messaging import update_motd_messages @@ -49,9 +48,9 @@ return False try: - LOG.debug("Running job: %s", self.name) + LOG.info("Running job: %s", self.name) if self._job_func(cfg=cfg): - LOG.debug("Executed job: %s", self.name) + LOG.info("Executed job: %s", self.name) except Exception as e: LOG.error("Error executing job %s: %s", self.name, str(e)) return False @@ -180,10 +179,7 @@ if __name__ == "__main__": - setup_journald_logging(logging.DEBUG, LOG) - # Make sure the ubuntupro.timer logger does not generate double logging - LOG.propagate = False - setup_journald_logging(logging.ERROR, logging.getLogger("ubuntupro")) + log.setup_journald_logging() cfg = UAConfig() current_time = datetime.now(timezone.utc) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/lib/upgrade_lts_contract.py ubuntu-advantage-tools-32~16.04/lib/upgrade_lts_contract.py --- ubuntu-advantage-tools-31.2.3~16.04/lib/upgrade_lts_contract.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/lib/upgrade_lts_contract.py 2024-04-23 13:37:02.000000000 +0000 @@ -7,13 +7,12 @@ import logging -from uaclient import http, upgrade_lts_contract -from uaclient.cli import setup_logging -from uaclient.config import UAConfig +from uaclient import config, defaults, http, log, upgrade_lts_contract if __name__ == "__main__": - setup_logging(logging.DEBUG) - cfg = UAConfig() + log.setup_cli_logging(logging.DEBUG, defaults.CONFIG_DEFAULTS["log_level"]) + cfg = config.UAConfig() + log.setup_cli_logging(cfg.log_level, cfg.log_file) http.configure_web_proxy(cfg.http_proxy, cfg.https_proxy) upgrade_lts_contract.process_contract_delta_after_apt_lock(cfg) upgrade_lts_contract.remove_private_esm_apt_cache() diff -Nru ubuntu-advantage-tools-31.2.3~16.04/requirements.tiobe.txt ubuntu-advantage-tools-32~16.04/requirements.tiobe.txt --- ubuntu-advantage-tools-31.2.3~16.04/requirements.tiobe.txt 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/requirements.tiobe.txt 2024-05-10 17:07:05.000000000 +0000 @@ -0,0 +1,3 @@ +flake8 +pylint +bandit \ No newline at end of file diff -Nru ubuntu-advantage-tools-31.2.3~16.04/sru/release-31/binary-package-rename-uninstall-old-not-allowed.sh ubuntu-advantage-tools-32~16.04/sru/release-31/binary-package-rename-uninstall-old-not-allowed.sh --- ubuntu-advantage-tools-31.2.3~16.04/sru/release-31/binary-package-rename-uninstall-old-not-allowed.sh 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/sru/release-31/binary-package-rename-uninstall-old-not-allowed.sh 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,61 @@ +#!/bin/bash +set -e + +series=$1 +install_from=$2 # 'staging', or 'proposed', or the name of a ppa + +name=$series-test + +function cleanup { + lxc delete $name --force +} + +function on_err { + echo -e "Test Failed" + cleanup + exit 1 +} +trap on_err ERR + + +lxc launch ubuntu-daily:$series $name +sleep 5 + +# Install latest ubuntu-advantage-tools +lxc exec $name -- apt-get update > /dev/null +lxc exec $name -- apt-get install -y ubuntu-advantage-tools > /dev/null + +# Set up source for installing new version +# ---------------------------------------------------------------- +if [ $install_from == 'staging' ]; then + lxc exec $name -- sudo add-apt-repository ppa:ua-client/staging -y > /dev/null + lxc exec $name -- apt-get update > /dev/null +elif [ $install_from == 'proposed' ]; then + lxc exec $name -- sh -c "echo \"deb http://archive.ubuntu.com/ubuntu $series-proposed main\" | tee /etc/apt/sources.list.d/proposed.list" + lxc exec $name -- apt-get update + lxc exec $name -- sh -c "cat > /etc/apt/preferences.d/pro-posed << EOF +Package: ubuntu-advantage-tools ubuntu-pro-client ubuntu-pro-auto-attach ubuntu-advantage-pro ubuntu-pro-client-l10n +Pin: release a=$series-proposed +Pin-Priority: 600 +EOF" +else + lxc exec $name -- sudo add-apt-repository $install_from -y > /dev/null + lxc exec $name -- apt-get update > /dev/null +fi +# ---------------------------------------------------------------- + +echo -e "\n* Latest u-a-t is installed from -updates and -proposed enabled with new version available" +echo "###########################################" +lxc exec $name -- apt-cache policy ubuntu-advantage-tools ubuntu-pro-client +echo -e "###########################################\n" + +echo -e "\n* Attempt to upgrade to ubuntu-pro-client while simultaneously removing ubuntu-advantage-tools without removing ubuntu-minimal" +echo -e "* This should fail" +echo "###########################################" +set -x +lxc exec $name -- apt install ubuntu-pro-client ubuntu-advantage-tools- ubuntu-minimal -y && RC=$? || RC=$? +test $RC -eq 100 +set +x +echo -e "###########################################\n" + +cleanup diff -Nru ubuntu-advantage-tools-31.2.3~16.04/sru/release-31/binary-package-renames-install-upc.sh ubuntu-advantage-tools-32~16.04/sru/release-31/binary-package-renames-install-upc.sh --- ubuntu-advantage-tools-31.2.3~16.04/sru/release-31/binary-package-renames-install-upc.sh 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/sru/release-31/binary-package-renames-install-upc.sh 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,112 @@ +#!/bin/bash +set -e + +series=$1 +install_from=$2 # 'staging', or 'proposed', or the name of a ppa +version=$3 + +name=$series-test + +function cleanup { + lxc delete $name --force +} + +function on_err { + echo -e "Test Failed" + cleanup + exit 1 +} +trap on_err ERR + + +lxc launch ubuntu-daily:$series $name +sleep 5 + +# Install latest ubuntu-advantage-tools +lxc exec $name -- apt-get update > /dev/null +lxc exec $name -- apt-get install -y ubuntu-advantage-tools > /dev/null + +# Set up source for installing new version +# ---------------------------------------------------------------- +if [ $install_from == 'staging' ]; then + lxc exec $name -- sudo add-apt-repository ppa:ua-client/staging -y > /dev/null + lxc exec $name -- apt-get update > /dev/null +elif [ $install_from == 'proposed' ]; then + lxc exec $name -- sh -c "echo \"deb http://archive.ubuntu.com/ubuntu $series-proposed main\" | tee /etc/apt/sources.list.d/proposed.list" + lxc exec $name -- apt-get update > /dev/null + lxc exec $name -- sh -c "cat > /etc/apt/preferences.d/pro-posed << EOF +Package: ubuntu-advantage-tools ubuntu-pro-client ubuntu-pro-auto-attach ubuntu-advantage-pro ubuntu-pro-client-l10n +Pin: release a=$series-proposed +Pin-Priority: 600 +EOF" +else + lxc exec $name -- sudo add-apt-repository $install_from -y > /dev/null + lxc exec $name -- apt-get update > /dev/null +fi +# ---------------------------------------------------------------- + +echo -e "\n* Latest u-a-t is installed from -updates and 31.2 is available in -proposed" +echo "###########################################" +lxc exec $name -- apt-cache policy ubuntu-advantage-tools ubuntu-pro-client +echo -e "###########################################\n" + +echo -e "\n* attach prior to upgrade" +echo "###########################################" +lxc exec $name -- pro attach $UACLIENT_BEHAVE_CONTRACT_TOKEN +echo -e "###########################################\n" + +echo -e "\n* upgrade to rename" +echo "###########################################" +set -x +# uncomment one of these per run +# lxc exec $name -- apt install ubuntu-advantage-tools -y +# lxc exec $name -- apt install ubuntu-pro-client -y +lxc exec $name -- apt install ubuntu-pro-client=$version -y +# lxc exec $name -- apt upgrade -y +# lxc exec $name -- apt dist-upgrade -y +set +x +echo -e "###########################################\n" + +echo -e "\n* autoremove just to make sure it doesn't do anything unexpected" +echo "###########################################" +set -x +lxc exec $name -- apt autoremove -y +set +x +echo -e "###########################################\n" + +echo -e "\n* still attached" +echo "###########################################" +lxc exec $name -- pro status +echo -e "###########################################\n" + +echo -e "\n* appropriate versions of ubuntu-advantage-tools and ubuntu-pro-client are installed" +echo "###########################################" +lxc exec $name -- apt policy ubuntu-advantage-tools ubuntu-pro-client +echo -e "###########################################\n" + +echo -e "\n* dpkg doesn't show obsolete conffiles for ubuntu-advantage-tools" +echo "###########################################" +lxc exec $name -- dpkg-query --showformat='${Conffiles}\n' --show ubuntu-advantage-tools +echo -e "###########################################\n" + +if [ "$series" != "xenial" ] && [ "$series" != "bionic" ]; then + echo -e "\n* reinstall just to make sure it doesn't do anything unexpected" + echo "###########################################" + set -x + lxc exec $name -- apt reinstall ubuntu-advantage-tools + lxc exec $name -- apt reinstall ubuntu-pro-client + set +x + echo -e "###########################################\n" + + echo -e "\n* still attached" + echo "###########################################" + lxc exec $name -- pro status + echo -e "###########################################\n" + + echo -e "\n* appropriate versions of ubuntu-advantage-tools and ubuntu-pro-client are installed" + echo "###########################################" + lxc exec $name -- apt policy ubuntu-advantage-tools ubuntu-pro-client + echo -e "###########################################\n" +fi + +cleanup diff -Nru ubuntu-advantage-tools-31.2.3~16.04/sru/release-31/test-jobs-status-world-readable.sh ubuntu-advantage-tools-32~16.04/sru/release-31/test-jobs-status-world-readable.sh --- ubuntu-advantage-tools-31.2.3~16.04/sru/release-31/test-jobs-status-world-readable.sh 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/sru/release-31/test-jobs-status-world-readable.sh 2024-04-23 13:37:02.000000000 +0000 @@ -45,6 +45,11 @@ elif [ $install_from == 'proposed' ]; then lxc exec $name -- sh -c "echo \"deb http://archive.ubuntu.com/ubuntu $series-proposed main\" | tee /etc/apt/sources.list.d/proposed.list" lxc exec $name -- apt-get update > /dev/null + lxc exec $name -- sh -c "cat > /etc/apt/preferences.d/pro-posed << EOF +Package: ubuntu-advantage-tools ubuntu-pro-client ubuntu-pro-auto-attach ubuntu-advantage-pro ubuntu-pro-client-l10n +Pin: release a=$series-proposed +Pin-Priority: 600 +EOF" lxc exec $name -- apt-get install ubuntu-advantage-tools -y > /dev/null else lxc file push $install_from $name/new-ua.deb @@ -52,8 +57,14 @@ fi # ---------------------------------------------------------------- +echo -e "\n* Installed u-a-t from -proposed" +echo "###########################################" +lxc exec $name -- apt-cache policy ubuntu-advantage-tools ubuntu-pro-client +echo -e "###########################################\n" + echo -e "\n* re-create jobs-status file and notice that is now world-readable" echo "###########################################" +lxc exec $name -- rm /var/lib/ubuntu-advantage/jobs-status.json lxc exec $name -- python3 /usr/lib/ubuntu-advantage/timer.py lxc exec $name -- ls -la /var/lib/ubuntu-advantage/jobs-status.json echo -e "###########################################\n" diff -Nru ubuntu-advantage-tools-31.2.3~16.04/sru/release-32/test-user-config-created.sh ubuntu-advantage-tools-32~16.04/sru/release-32/test-user-config-created.sh --- ubuntu-advantage-tools-31.2.3~16.04/sru/release-32/test-user-config-created.sh 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/sru/release-32/test-user-config-created.sh 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,76 @@ +#!/bin/bash +set -e + +series=$1 +install_from=$2 # either path to a .deb, or 'staging', or 'proposed' + +name=$series-dev + +function cleanup { + lxc delete $name --force +} + +function on_err { + echo -e "Test Failed" + cleanup + exit 1 +} +trap on_err ERR + + +lxc launch ubuntu-daily:$series $name +sleep 5 + +# Install latest ubuntu-advantage-tools +lxc exec $name -- apt-get update > /dev/null +lxc exec $name -- apt-get install -y ubuntu-advantage-tools > /dev/null +echo -e "\n* Latest u-a-t is installed" +echo "###########################################" +lxc exec $name -- apt-cache policy ubuntu-advantage-tools +echo -e "###########################################\n" + +# Create user-config.json file with the given content +http_proxy_value="http://someuser:somepassword@example.com:3128" +# Create user-config.json file with the given content +lxc exec $name -- sh -c "echo '{\"http_proxy\": \"$http_proxy_value\"}' > /var/lib/ubuntu-advantage/user-config.json" + +# ---------------------------------------------------------------- +if [ $install_from == 'staging' ]; then + lxc exec $name -- sudo add-apt-repository ppa:ua-client/staging -y > /dev/null + lxc exec $name -- apt-get update > /dev/null + lxc exec $name -- apt-get install ubuntu-advantage-tools -y > /dev/null +elif [ $install_from == 'proposed' ]; then + lxc exec $name -- sh -c "echo \"deb http://archive.ubuntu.com/ubuntu $series-proposed main\" | tee /etc/apt/sources.list.d/proposed.list" + lxc exec $name -- apt-get update > /dev/null + lxc exec $name -- apt-get install ubuntu-advantage-tools -y > /dev/null +else + lxc file push $install_from $name/new-ua.deb + lxc exec $name -- dpkg -i /new-ua.deb > /dev/null +fi +# ---------------------------------------------------------------- + +# Check if user-config.json is moved to the private directory +lxc exec $name -- test -e /var/lib/ubuntu-advantage/private/user-config.json; +private_config_contents=$(lxc exec $name -- cat /var/lib/ubuntu-advantage/private/user-config.json) +private_http_proxy_value=$(echo "$private_config_contents" | jq -r '.http_proxy') +# Check if the contents are the same as the previous contents +if [ "$http_proxy_value" == "$private_http_proxy_value" ]; then + echo "Contents of private/user-config.json have not changed" +fi +# Check if the file permissions are root +echo "Checking file permissions for private/user-config.json:" +lxc exec $name -- stat -c %A /var/lib/ubuntu-advantage/private/user-config.json | grep -- "-rw-------" + +# Check if a new public file is created +lxc exec $name -- test -e /var/lib/ubuntu-advantage/user-config.json; +public_config_contents=$(lxc exec $name -- cat /var/lib/ubuntu-advantage/user-config.json) +public_http_proxy_value=$(echo "$public_config_contents" | jq -r '.http_proxy') +# Check if the public_http_proxy_value is "" +if [ "$public_http_proxy_value" = "" ]; then + echo "public_http_proxy_value is " +fi +# Check if file permissions are public +echo "Checking file permissions for public/user-config.json:" +lxc exec $name -- stat -c %A /var/lib/ubuntu-advantage/user-config.json | grep -- "-rw-r--r--" + +cleanup \ No newline at end of file diff -Nru ubuntu-advantage-tools-31.2.3~16.04/systemd/esm-cache.service ubuntu-advantage-tools-32~16.04/systemd/esm-cache.service --- ubuntu-advantage-tools-31.2.3~16.04/systemd/esm-cache.service 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/systemd/esm-cache.service 2024-04-23 13:37:02.000000000 +0000 @@ -13,3 +13,13 @@ [Service] Type=oneshot ExecStart=/usr/bin/python3 /usr/lib/ubuntu-advantage/esm_cache.py +AppArmorProfile=-ubuntu_pro_esm_cache +CapabilityBoundingSet=~CAP_SYS_ADMIN +CapabilityBoundingSet=~CAP_NET_ADMIN +CapabilityBoundingSet=~CAP_NET_BIND_SERVICE +CapabilityBoundingSet=~CAP_SYS_PTRACE +CapabilityBoundingSet=~CAP_NET_RAW +PrivateTmp=true +RestrictAddressFamilies=~AF_NETLINK +RestrictAddressFamilies=~AF_PACKET + diff -Nru ubuntu-advantage-tools-31.2.3~16.04/tools/spellcheck-allowed-words.txt ubuntu-advantage-tools-32~16.04/tools/spellcheck-allowed-words.txt --- ubuntu-advantage-tools-31.2.3~16.04/tools/spellcheck-allowed-words.txt 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/tools/spellcheck-allowed-words.txt 2024-04-23 13:37:02.000000000 +0000 @@ -23,6 +23,7 @@ aws bootable Cancelling +cancelled Canonical's cfg CFG diff -Nru ubuntu-advantage-tools-31.2.3~16.04/tox.ini ubuntu-advantage-tools-32~16.04/tox.ini --- ubuntu-advantage-tools-31.2.3~16.04/tox.ini 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/tox.ini 2024-04-23 13:37:02.000000000 +0000 @@ -1,5 +1,5 @@ [tox] -envlist = test, flake8, mypy, black, isort, shellcheck +envlist = test, flake8, mypy, black, reformat-gherkin, isort, shellcheck [testenv] allowlist_externals=/usr/bin/bash @@ -9,6 +9,7 @@ mypy: -rtypes-requirements.txt black: -rdev-requirements.txt isort: -rdev-requirements.txt + reformat-gherkin: -rdev-requirements.txt behave: -rintegration-requirements.txt shellcheck: -rdev-requirements.txt passenv = @@ -22,6 +23,7 @@ flake8: flake8 uaclient lib setup.py features mypy: mypy uaclient/ features/ lib/ black: black --check --diff uaclient/ features/ lib/ setup.py + reformat-gherkin: reformat-gherkin --check features/ isort: isort --check --diff uaclient/ features/ lib/ setup.py shellcheck: bash -O extglob -O nullglob -c "shellcheck -S warning tools/*.sh debian/*.{config,postinst,postrm,prerm} lib/*.sh sru/*.sh update-motd.d/*" behave: behave -v {posargs} diff -Nru ubuntu-advantage-tools-31.2.3~16.04/types-requirements.txt ubuntu-advantage-tools-32~16.04/types-requirements.txt --- ubuntu-advantage-tools-31.2.3~16.04/types-requirements.txt 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/types-requirements.txt 2024-04-23 13:37:02.000000000 +0000 @@ -2,3 +2,5 @@ types-PyYAML types-toml types-pycurl +types-paramiko +types-requests \ No newline at end of file diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/actions.py ubuntu-advantage-tools-32~16.04/uaclient/actions.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/actions.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/actions.py 2024-05-10 17:07:05.000000000 +0000 @@ -1,5 +1,6 @@ import datetime import glob +import json import logging import os import re @@ -7,18 +8,20 @@ from typing import List, Optional # noqa: F401 from uaclient import ( + api, clouds, config, contract, entitlements, + event_logger, exceptions, livepatch, ) from uaclient import log as pro_log +from uaclient import messages, secret_manager from uaclient import status as ua_status from uaclient import system, timer, util from uaclient.clouds import AutoAttachCloudInstance # noqa: F401 -from uaclient.clouds import identity from uaclient.defaults import ( APPARMOR_PROFILES, CLOUD_BUILD_INFO, @@ -28,9 +31,11 @@ from uaclient.files.state_files import ( AttachmentData, attachment_data_file, + machine_id_file, timer_jobs_state_file, ) +event = event_logger.get_event_logger() LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) @@ -48,8 +53,92 @@ USER_LOG_COLLECTED_LIMIT = 10 +def _handle_partial_attach( + cfg: config.UAConfig, + contract_client: contract.UAContractClient, + attached_at: datetime.datetime, +): + from uaclient.timer.update_messaging import update_motd_messages + + attachment_data_file.write(AttachmentData(attached_at=attached_at)) + ua_status.status(cfg=cfg) + update_motd_messages(cfg) + contract_client.update_activity_token() + + +def _enable_default_services( + cfg: config.UAConfig, + services_to_be_enabled: List[contract.EnableByDefaultService], + contract_client: contract.UAContractClient, + attached_at: datetime.datetime, + silent: bool = False, +): + ret = True + failed_services = [] + unexpected_errors = [] + + try: + for enable_by_default_service in services_to_be_enabled: + ent_ret, reason = enable_entitlement_by_name( + cfg=cfg, + name=enable_by_default_service.name, + assume_yes=True, + allow_beta=True, + variant=enable_by_default_service.variant, + silent=silent, + ) + ret &= ent_ret + + if not ent_ret: + failed_services.append(enable_by_default_service.name) + else: + event.service_processed(service=enable_by_default_service.name) + except exceptions.ConnectivityError as exc: + event.service_failed(enable_by_default_service.name) + _handle_partial_attach(cfg, contract_client, attached_at) + raise exc + except exceptions.UbuntuProError: + failed_services.append(enable_by_default_service.name) + ret = False + except Exception as e: + ret = False + failed_services.append(enable_by_default_service.name) + unexpected_errors.append(e) + + if not ret: + # Persist updated status in the event of partial attach + _handle_partial_attach(cfg, contract_client, attached_at) + event.services_failed(failed_services) + + if unexpected_errors: + raise exceptions.AttachFailureUnknownError( + failed_services=[ + ( + name, + messages.UNEXPECTED_ERROR.format( + error_msg=str(exception), + log_path=pro_log.get_user_or_root_log_file_path(), + ), + ) + for name, exception in zip( + failed_services, unexpected_errors + ) + ] + ) + else: + raise exceptions.AttachFailureDefaultServices( + failed_services=[ + (name, messages.E_ATTACH_FAILURE_DEFAULT_SERVICES) + for name in failed_services + ] + ) + + def attach_with_token( - cfg: config.UAConfig, token: str, allow_enable: bool + cfg: config.UAConfig, + token: str, + allow_enable: bool, + silent: bool = False, ) -> None: """ Common functionality to take a token and attach via contract backend @@ -58,8 +147,12 @@ :raise ContractAPIError: On unexpected errors when talking to the contract server. """ + from uaclient.entitlements import ( + check_entitlement_apt_directives_are_unique, + ) from uaclient.timer.update_messaging import update_motd_messages + secret_manager.secrets.add_secret(token) contract_client = contract.UAContractClient(cfg) attached_at = datetime.datetime.now(tz=datetime.timezone.utc) new_machine_token = contract_client.add_contract_machine( @@ -68,33 +161,29 @@ cfg.machine_token_file.write(new_machine_token) + try: + check_entitlement_apt_directives_are_unique(cfg) + except exceptions.EntitlementsAPTDirectivesAreNotUnique as e: + cfg.machine_token_file.delete() + raise e + system.get_machine_id.cache_clear() machine_id = new_machine_token.get("machineTokenInfo", {}).get( "machineId", system.get_machine_id(cfg) ) - cfg.write_cache("machine-id", machine_id) + machine_id_file.write(machine_id) - try: - contract.process_entitlements_delta( - cfg, - {}, - # we load from the file here instead of using the response - # so that we get any machine_token_overlay present during testing - # TODO: decide if there is a better way to do this - cfg.machine_token_file.entitlements, - allow_enable, + if allow_enable: + services_to_be_enabled = contract.get_enabled_by_default_services( + cfg, cfg.machine_token_file.entitlements + ) + _enable_default_services( + cfg=cfg, + services_to_be_enabled=services_to_be_enabled, + contract_client=contract_client, + attached_at=attached_at, + silent=silent, ) - except (exceptions.ConnectivityError, exceptions.UbuntuProError) as exc: - # Persist updated status in the event of partial attach - attachment_data_file.write(AttachmentData(attached_at=attached_at)) - ua_status.status(cfg=cfg) - update_motd_messages(cfg) - contract_client.update_activity_token() - raise exc - - current_iid = identity.get_instance_id() - if current_iid: - cfg.write_cache("instance-id", current_iid) attachment_data_file.write(AttachmentData(attached_at=attached_at)) update_motd_messages(cfg) @@ -132,6 +221,7 @@ allow_beta: bool = False, access_only: bool = False, variant: str = "", + silent: bool = False, extra_args: Optional[List[str]] = None ): """ @@ -151,7 +241,16 @@ access_only=access_only, extra_args=extra_args, ) - return entitlement.enable() + + if not silent: + event.info(messages.ENABLING_TMPL.format(title=entitlement.title)) + + ent_ret, reason = entitlement.enable(api.ProgressWrapper()) + + if ent_ret and not silent: + event.info(messages.ENABLED_TMPL.format(title=entitlement.title)) + + return ent_ret, reason def status( @@ -183,6 +282,7 @@ """ # can't use journalctl's --grep, because xenial doesn't support it :/ cmd = ["journalctl", "-b", "-k", "--since=1 day ago"] + # all profiles are prefixed with "ubuntu_pro_" apparmor_re = r"apparmor=\".*(profile=\"ubuntu_pro_|name=\"ubuntu_pro_)" kernel_logs = None try: @@ -235,9 +335,6 @@ "cloud-id", "{}/cloud-id.txt".format(output_dir) ) _write_command_output_to_file( - "pro status --format json", "{}/ua-status.json".format(output_dir) - ) - _write_command_output_to_file( "{} status".format(livepatch.LIVEPATCH_CMD), "{}/livepatch-status.txt".format(output_dir), ) @@ -268,6 +365,16 @@ "{}/{}.txt".format(output_dir, service), return_codes=[0, 3], ) + pro_status, _ = status(cfg=cfg, show_all=False) + system.write_file( + "{}/pro-status.json".format(output_dir), + json.dumps(pro_status, cls=util.DatetimeAwareJSONEncoder), + ) + env_vars = util.get_pro_environment() + system.write_file( + "{}/environment_vars.json".format(output_dir), + json.dumps(env_vars), + ) state_files = _get_state_files(cfg) user_log_files = ( diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/__init__.py ubuntu-advantage-tools-32~16.04/uaclient/api/__init__.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/__init__.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/__init__.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,4 +1,76 @@ +import abc import logging +from typing import Optional # setup null handler for all API endpoints logging.getLogger("ubuntupro").addHandler(logging.NullHandler()) + + +class AbstractProgress(metaclass=abc.ABCMeta): + @abc.abstractmethod + def progress( + self, + *, + total_steps: int, + done_steps: int, + previous_step_message: Optional[str], + current_step_message: Optional[str] + ): + pass + + +class NullProgress(AbstractProgress): + def progress( + self, + *, + total_steps: int, + done_steps: int, + previous_step_message: Optional[str], + current_step_message: Optional[str] + ): + pass + + +class ProgressWrapper: + def __init__(self, progress_object: Optional[AbstractProgress] = None): + if progress_object is not None: + self.progress_object = progress_object + else: + self.progress_object = NullProgress() + self.done_steps = 0 + self.total_steps = -1 + self.previous_step_message = None # type: Optional[str] + + def progress(self, message: str): + self.progress_object.progress( + total_steps=self.total_steps, + done_steps=self.done_steps, + previous_step_message=self.previous_step_message, + current_step_message=message, + ) + self.previous_step_message = message + self.done_steps += 1 + + def finish(self): + self.done_steps = self.total_steps + self.progress_object.progress( + total_steps=self.total_steps, + done_steps=self.done_steps, + previous_step_message=self.previous_step_message, + current_step_message=None, + ) + + def emit(self, event: str, payload=None): + """ + This is our secret event system. We use it internally to insert prompts + and extra messages in the middle of operations at certain points. + We don't consider this stable enough to expose to the public API. + """ + if hasattr(self.progress_object, "_on_event"): + self.progress_object._on_event(event, payload) + + def is_interactive(self) -> bool: + if hasattr(self.progress_object, "is_interactive"): + return self.progress_object.is_interactive + else: + return False diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/api.py ubuntu-advantage-tools-32~16.04/uaclient/api/api.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/api.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/api.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,8 +1,8 @@ import json from importlib import import_module -from typing import Any, Callable, Dict, List, Tuple +from typing import Any, Callable, Dict, List, Optional, Tuple -from uaclient.api import errors +from uaclient.api import AbstractProgress, errors from uaclient.api.data_types import APIData, APIResponse, ErrorWarningObject from uaclient.config import UAConfig from uaclient.data_types import IncorrectFieldTypeError @@ -16,6 +16,8 @@ "u.pro.attach.magic.initiate.v1", "u.pro.attach.magic.revoke.v1", "u.pro.attach.magic.wait.v1", + "u.pro.attach.token.full_token_attach.v1", + "u.pro.detach.v1", "u.pro.packages.summary.v1", "u.pro.packages.updates.v1", "u.pro.security.fix.cve.execute.v1", @@ -24,6 +26,9 @@ "u.pro.security.fix.usn.plan.v1", "u.pro.security.status.livepatch_cves.v1", "u.pro.security.status.reboot_required.v1", + "u.pro.services.dependencies.v1", + "u.pro.services.disable.v1", + "u.pro.services.enable.v1", "u.pro.status.enabled_services.v1", "u.pro.status.is_attached.v1", "u.pro.version.v1", @@ -74,9 +79,6 @@ raise errors.APIJSONDataFormatError(data=data) for k, v in json_data.items(): - if not k or not v: - raise errors.APIBadArgsFormat(arg="{}:{}".format(k, v)) - if k not in fields: warnings.append( ErrorWarningObject( @@ -92,7 +94,11 @@ def call_api( - endpoint_path: str, options: List[str], data: str, cfg: UAConfig + endpoint_path: str, + options: List[str], + data: str, + cfg: UAConfig, + progress_object: Optional[AbstractProgress] = None, ) -> APIResponse: if endpoint_path not in VALID_ENDPOINTS: @@ -126,7 +132,12 @@ ) try: - result = endpoint.fn(options, cfg) + if endpoint.supports_progress: + result = endpoint.fn( + options, cfg, progress_object=progress_object + ) + else: + result = endpoint.fn(options, cfg) except Exception as e: return errors.error_out(e) @@ -136,7 +147,10 @@ errors.APINoArgsForEndpoint(endpoint=endpoint_path) ) try: - result = endpoint.fn(cfg) + if endpoint.supports_progress: + result = endpoint.fn(cfg, progress_object=progress_object) + else: + result = endpoint.fn(cfg) except Exception as e: return errors.error_out(e) @@ -170,8 +184,10 @@ name: str, fn: Callable, options_cls, + supports_progress: bool = False, ): self.version = version self.name = name self.fn = fn self.options_cls = options_cls + self.supports_progress = supports_progress diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/exceptions.py ubuntu-advantage-tools-32~16.04/uaclient/api/exceptions.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/exceptions.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/exceptions.py 2024-04-23 13:37:02.000000000 +0000 @@ -4,12 +4,18 @@ AlreadyAttachedError, ConnectivityError, ContractAPIError, + EntitlementNotDisabledError, + EntitlementNotEnabledError, EntitlementNotFoundError, EntitlementsNotEnabledError, + IncompatibleServiceStopsEnable, InvalidProImage, LockHeldError, NonAutoAttachImageError, + NonRootUserError, + RequiredServiceStopsEnable, UbuntuProError, + UnattachedError, UrlError, UserFacingError, ) @@ -22,10 +28,16 @@ "InvalidProImage", "LockHeldError", "NonAutoAttachImageError", + "NonRootUserError", "UbuntuProError", + "UnattachedError", "UrlError", "UserFacingError", "EntitlementsNotEnabledError", + "EntitlementNotEnabledError", + "EntitlementNotDisabledError", + "IncompatibleServiceStopsEnable", + "RequiredServiceStopsEnable", ] @@ -35,3 +47,7 @@ class UnattendedUpgradesError(APIError): _formatted_msg = messages.E_UNATTENDED_UPGRADES_ERROR + + +class NotSupported(UbuntuProError): + _msg = messages.E_NOT_SUPPORTED diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api.py ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api.py 2024-04-23 13:37:02.000000000 +0000 @@ -163,12 +163,22 @@ assert warning.meta == {} @pytest.mark.parametrize( - "options_cls,arguments", - ((None, []), (mock.MagicMock(), ["key=value"])), + ["options_cls", "supports_progress", "arguments"], + ( + (None, False, []), + (mock.MagicMock(), False, ["key=value"]), + (None, True, []), + (mock.MagicMock(), True, ["key=value"]), + ), ) @mock.patch("uaclient.api.api.import_module") def test_call_endpoint( - self, m_import_module, options_cls, arguments, FakeConfig + self, + m_import_module, + options_cls, + supports_progress, + arguments, + FakeConfig, ): mock_endpoint = mock.MagicMock() mock_endpoint.options_cls = options_cls @@ -176,6 +186,8 @@ mock_endpoint_fn = mock.MagicMock() mock_endpoint.fn = mock_endpoint_fn + mock_endpoint.supports_progress = supports_progress + m_import_module.return_value.endpoint = mock_endpoint cfg = FakeConfig() @@ -192,11 +204,25 @@ assert options_cls.from_dict.call_args_list == [ mock.call({"key": "value"}) ] - assert mock_endpoint_fn.call_args_list == [ - mock.call(options_cls.from_dict.return_value, cfg) - ] + if supports_progress: + assert mock_endpoint_fn.call_args_list == [ + mock.call( + options_cls.from_dict.return_value, + cfg, + progress_object=mock.ANY, + ) + ] + else: + assert mock_endpoint_fn.call_args_list == [ + mock.call(options_cls.from_dict.return_value, cfg) + ] else: - assert mock_endpoint_fn.call_args_list == [mock.call(cfg)] + if supports_progress: + assert mock_endpoint_fn.call_args_list == [ + mock.call(cfg, progress_object=mock.ANY) + ] + else: + assert mock_endpoint_fn.call_args_list == [mock.call(cfg)] @mock.patch("uaclient.api.errors.error_out") @mock.patch("uaclient.api.api.import_module") @@ -325,6 +351,7 @@ mock_endpoint = mock.MagicMock(options_cls=m_options_cls) mock_endpoint_fn = mock.MagicMock() mock_endpoint.fn = mock_endpoint_fn + mock_endpoint.supports_progress = False m_import_module.return_value.endpoint = mock_endpoint data = '{"test": ["1", "2"]}' diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_attach_auto_full_auto_attach_v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_attach_auto_full_auto_attach_v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_attach_auto_full_auto_attach_v1.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_attach_auto_full_auto_attach_v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -168,6 +168,7 @@ @mock.patch("uaclient.files.notices.add") @mock.patch("uaclient.files.notices.remove") class TestFullAutoAttachV1: + @mock.patch("uaclient.lock.RetryLock.__enter__") @mock.patch( M_PATH + "contract.UAContractClient.update_activity_token", ) @@ -182,6 +183,7 @@ _cloud_instance_factory, m_enable_ent_by_name, _m_update_activity_token, + _m_lock_enter, _notice_remove, _notice_add, FakeConfig, @@ -204,6 +206,7 @@ assert 5 == m_enable_ent_by_name.call_count + @mock.patch("uaclient.lock.RetryLock.__enter__") @mock.patch( M_PATH + "contract.UAContractClient.update_activity_token", ) @@ -219,6 +222,7 @@ _cloud_instance_factory, enable_ent_by_name, _m_update_activity_token, + _m_lock_enter, _notice_remove, _notice_add, FakeConfig, diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_attach_full_token_attach.py ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_attach_full_token_attach.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_attach_full_token_attach.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_attach_full_token_attach.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,120 @@ +import mock +import pytest + +from uaclient import exceptions, messages +from uaclient.api.data_types import ErrorWarningObject +from uaclient.api.u.pro.attach.token.full_token_attach.v1 import ( + FullTokenAttachResult, + _full_token_attach, +) + +M_PATH = "uaclient.api.u.pro.attach.token.full_token_attach.v1." + + +@mock.patch("uaclient.lock.RetryLock.__enter__") +class TestFullTokenAttach: + @mock.patch("uaclient.util.we_are_currently_root") + def test_full_token_attach_non_root_user( + self, m_we_are_currently_root, _m_lock_enter + ): + m_we_are_currently_root.return_value = False + + with pytest.raises(exceptions.NonRootUserError): + _full_token_attach(None, None) + + @mock.patch(M_PATH + "_is_attached") + def test_full_token_attach_when_already_attached( + self, m_is_attached, _m_lock_enter + ): + m_is_attached.return_value = mock.MagicMock(is_attached=True) + assert FullTokenAttachResult( + enabled=[], reboot_required=False + ) == _full_token_attach(None, None) + + @pytest.mark.parametrize( + "test_exception,expected_result,expected_warnings", + ( + ( + exceptions.AttachFailureDefaultServices( + failed_services=[ + ("ent1", messages.E_ATTACH_FAILURE_DEFAULT_SERVICES), + ("ent2", messages.E_ATTACH_FAILURE_DEFAULT_SERVICES), + ] + ), + FullTokenAttachResult( + enabled=["ent3"], + reboot_required=True, + ), + [ + ErrorWarningObject( + title=messages.E_ATTACH_FAILURE_DEFAULT_SERVICES.msg, + code=messages.E_ATTACH_FAILURE_DEFAULT_SERVICES.name, + meta={"service": "ent1"}, + ), + ErrorWarningObject( + title=messages.E_ATTACH_FAILURE_DEFAULT_SERVICES.msg, + code=messages.E_ATTACH_FAILURE_DEFAULT_SERVICES.name, + meta={"service": "ent2"}, + ), + ], + ), + ( + exceptions.AttachFailureUnknownError( + failed_services=[ + ( + "ent1", + messages.UNEXPECTED_ERROR.format( + error_msg="error", + log_path="path", + ), + ) + ] + ), + FullTokenAttachResult( + enabled=["ent3"], + reboot_required=True, + ), + [ + ErrorWarningObject( + title=messages.UNEXPECTED_ERROR.format( + error_msg="error", + log_path="path", + ).msg, + code=messages.UNEXPECTED_ERROR.name, + meta={"service": "ent1"}, + ), + ], + ), + ), + ) + @mock.patch(M_PATH + "_is_attached") + @mock.patch(M_PATH + "_enabled_services") + @mock.patch(M_PATH + "_reboot_required") + @mock.patch(M_PATH + "attach_with_token") + def test_failed_services_during_attach( + self, + m_attach_with_token, + m_reboot_required, + m_enabled_services, + m_is_attached, + _m_lock_enter, + test_exception, + expected_result, + expected_warnings, + ): + m_reboot_required.return_value = mock.MagicMock(reboot_required="yes") + m_is_attached.return_value = mock.MagicMock(is_attached=False) + m_ent = mock.MagicMock() + type(m_ent).name = mock.PropertyMock(return_value="ent3") + m_enabled_services.return_value = mock.MagicMock( + enabled_services=[m_ent] + ) + m_attach_with_token.side_effect = test_exception + + actual_result = _full_token_attach( + options=mock.MagicMock(token="token", auto_enable_services=True), + cfg=None, + ) + + assert expected_result == actual_result + assert expected_warnings == actual_result.warnings diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_detach_v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_detach_v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_detach_v1.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_detach_v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,145 @@ +import mock +import pytest + +from uaclient import entitlements, exceptions, messages +from uaclient.api.data_types import ErrorWarningObject +from uaclient.api.u.pro.detach.v1 import DetachResult, _detach +from uaclient.entitlements.entitlement_status import ( + CanDisableFailure, + CanDisableFailureReason, +) + +M_PATH = "uaclient.api.u.pro.detach.v1." + + +@mock.patch("uaclient.lock.RetryLock.__enter__") +class TestDetachV1: + @mock.patch(M_PATH + "_is_attached") + def test_detach_when_unattached(self, m_is_attached, _m_lock_enter): + m_is_attached.return_value = mock.MagicMock(is_attached=False) + assert DetachResult(disabled=[], reboot_required=False) == _detach( + mock.MagicMock() + ) + + @mock.patch("uaclient.util.we_are_currently_root") + def test_detach_when_non_root( + self, m_we_are_currently_root, _m_lock_enter + ): + m_we_are_currently_root.return_value = False + with pytest.raises(exceptions.NonRootUserError): + _detach(mock.MagicMock()) + + @mock.patch("uaclient.timer.stop") + @mock.patch("uaclient.daemon.start") + @mock.patch("uaclient.files.state_files.delete_state_files") + @mock.patch(M_PATH + "_reboot_required") + @mock.patch(M_PATH + "_is_attached") + @mock.patch(M_PATH + "update_motd_messages") + def test_detach( + self, + m_update_motd_messages, + m_is_attached, + m_reboot_required, + m_delete_state_files, + m_daemon_start, + m_timer_stop, + _m_lock_enter, + mock_entitlement, + ): + m_is_attached.return_value = mock.MagicMock(is_attached=True) + m_reboot_required.return_value = mock.MagicMock(reboot_required="yes") + + m_ent1_cls, _ = mock_entitlement( + name="ent1", + disable=(True, None), + can_disable=(True, None), + ) + + m_ent2_cls, _ = mock_entitlement( + name="ent2", + disable=(True, None), + can_disable=(True, None), + ) + + m_ent3_cls, _ = mock_entitlement( + name="ent3", + can_disable=(False, None), + ) + + m_ent4_cls, _ = mock_entitlement( + name="ent4", + can_disable=(True, None), + disable=( + False, + CanDisableFailure( + reason=CanDisableFailureReason.NOT_APPLICABLE, + message=messages.CANNOT_DISABLE_NOT_APPLICABLE.format( + title="ent4" + ), + ), + ), + ) + + m_ent5_cls, _ = mock_entitlement( + name="ent5", + can_disable=(True, None), + disable=( + False, + None, + ), + ) + + def ent_factory_side_effect(cfg, name): + if name == "ent1": + return m_ent1_cls + elif name == "ent2": + return m_ent2_cls + elif name == "ent3": + return m_ent3_cls + elif name == "ent4": + return m_ent4_cls + else: + return m_ent5_cls + + with mock.patch.object( + entitlements, + "entitlements_disable_order", + return_value=["ent2", "ent3", "ent5", "ent1", "ent4"], + ): + with mock.patch.object( + entitlements, "entitlement_factory" + ) as m_factory: + m_factory.side_effect = ent_factory_side_effect + m_cfg = mock.MagicMock() + m_machine_token = mock.MagicMock() + m_cfg.machine_token_file = m_machine_token + + actual_result = _detach(m_cfg) + + expected_warn_msg = messages.CANNOT_DISABLE_NOT_APPLICABLE.format( + title="ent4" + ) + expected_result = DetachResult( + disabled=["ent1", "ent2"], + reboot_required=True, + ) + expected_result.warnings = ( + [ + ErrorWarningObject( + title=messages.DISABLE_FAILED_TMPL.format(title="ent5"), + code="", + meta={"service": "ent_5"}, + ), + ErrorWarningObject( + title=expected_warn_msg.msg, + code=expected_warn_msg.name, + meta={"service": "ent_4"}, + ), + ], + ) + + assert expected_result == actual_result + assert 1 == m_daemon_start.call_count + assert 1 == m_timer_stop.call_count + assert 1 == m_delete_state_files.call_count + assert 1 == m_machine_token.delete.call_count diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_security_fix_plan.py ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_security_fix_plan.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_security_fix_plan.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_security_fix_plan.py 2024-04-23 13:37:02.000000000 +0000 @@ -14,6 +14,7 @@ AptUpgradeData, AttachData, EnableData, + FailUpdatingESMCacheData, FixPlanAptUpgradeStep, FixPlanAttachStep, FixPlanEnableStep, @@ -23,6 +24,7 @@ FixPlanNoOpStep, FixPlanResult, FixPlanUSNResult, + FixPlanWarningFailUpdatingESMCache, FixPlanWarningPackageCannotBeInstalled, FixPlanWarningSecurityIssueNotFixed, NoOpAlreadyFixedData, @@ -36,7 +38,6 @@ fix_plan_usn, ) from uaclient.api.u.pro.status.enabled_services.v1 import EnabledService -from uaclient.contract import ContractExpiryStatus from uaclient.messages import INVALID_SECURITY_ISSUE M_PATH = "uaclient.api.u.pro.security.fix._common.plan.v1." @@ -250,11 +251,11 @@ issue_id="cve-1234-1235", cfg=mock.MagicMock() ) + @mock.patch(M_PATH + "_should_update_esm_cache", return_value=False) @mock.patch(M_PATH + "merge_usn_released_binary_package_versions") @mock.patch(M_PATH + "get_cve_affected_source_packages_status") @mock.patch(M_PATH + "_enabled_services") @mock.patch(M_PATH + "_is_attached") - @mock.patch(M_PATH + "get_contract_expiry_status") @mock.patch("uaclient.apt.get_pkg_candidate_version") @mock.patch(M_PATH + "_get_cve_data") @mock.patch(M_PATH + "query_installed_source_pkg_versions") @@ -265,11 +266,11 @@ m_query_installed_pkgs, m_get_cve_data, m_get_pkg_candidate_version, - m_get_contract_expiry_status, m_is_attached, m_enabled_services, m_get_cve_affected_pkgs, m_merge_usn_pkgs, + _m_should_update_esm_cache, ): m_check_cve_fixed_by_livepatch.return_value = (None, None) m_query_installed_pkgs.return_value = { @@ -363,13 +364,22 @@ "1.6~esm1", "1.8.1~esm1", ] - m_get_contract_expiry_status.return_value = ( - ContractExpiryStatus.ACTIVE, - None, - ) m_is_attached.side_effect = [ - mock.MagicMock(is_attached=False), - mock.MagicMock(is_attached=True), + mock.MagicMock( + is_attached=False, + contract_status="none", + contract_remaining_days=0, + ), + mock.MagicMock( + is_attached=True, + contract_status="active", + contract_remaining_days=100, + ), + mock.MagicMock( + is_attached=True, + contract_status="active", + contract_remaining_days=100, + ), ] m_enabled_services.side_effect = [ mock.MagicMock(enabled_services=None), @@ -437,7 +447,6 @@ error=None, additional_data=AdditionalData(), ) - assert expected_plan == fix_plan_cve( issue_id="cve-1234-1235", cfg=mock.MagicMock() ) @@ -913,6 +922,137 @@ error=None, additional_data=AdditionalData(), ) + assert expected_plan == fix_plan_cve( + issue_id="cve-1234-1235", cfg=mock.MagicMock() + ) + + @mock.patch(M_PATH + "_enabled_services") + @mock.patch(M_PATH + "_is_attached") + @mock.patch("uaclient.apt.update_esm_caches") + @mock.patch(M_PATH + "_should_update_esm_cache", return_value=True) + @mock.patch(M_PATH + "merge_usn_released_binary_package_versions") + @mock.patch(M_PATH + "get_cve_affected_source_packages_status") + @mock.patch("uaclient.apt.get_pkg_candidate_version") + @mock.patch(M_PATH + "_get_cve_data") + @mock.patch(M_PATH + "query_installed_source_pkg_versions") + @mock.patch(M_PATH + "_check_cve_fixed_by_livepatch") + def test_fix_plan_for_cve_when_esm_cache_fails_to_be_updated( + self, + m_check_cve_fixed_by_livepatch, + m_query_installed_pkgs, + m_get_cve_data, + m_get_pkg_candidate_version, + m_get_cve_affected_pkgs, + m_merge_usn_pkgs, + m_should_update_esm_cache, + m_update_esm_caches, + m_is_attached, + m_enabled_services, + ): + m_update_esm_caches.side_effect = Exception("test") + m_is_attached.return_value = mock.MagicMock( + is_attached=False, + contract_status="none", + contract_remaining_days=0, + ) + m_enabled_services.return_value = mock.MagicMock(enabled_services=None) + m_check_cve_fixed_by_livepatch.return_value = (None, None) + m_query_installed_pkgs.return_value = { + "pkg1": { + "bin1": "1.0", + "bin2": "1.1", + }, + } + m_get_cve_data.return_value = ( + mock.MagicMock( + description="descr", + notices=[mock.MagicMock(title="test")], + ), + [], + ) + m_get_cve_affected_pkgs.return_value = { + "pkg1": CVEPackageStatus( + cve_response={ + "status": "released", + "pocket": "esm-infra", + } + ), + } + m_merge_usn_pkgs.return_value = { + "pkg1": { + "source": { + "description": "description", + "name": "pkg1", + "is_source": True, + "version": "1.1", + }, + "bin1": { + "is_source": False, + "name": "bin1", + "version": "1.1~esm1", + }, + "bin2": { + "is_source": False, + "name": "bin2", + "version": "1.2~esm1", + }, + }, + } + m_get_pkg_candidate_version.side_effect = ["1.1", "1.1"] + + expected_error_msg = messages.E_UPDATING_ESM_CACHE.format(error="test") + expected_plan = FixPlanResult( + title="CVE-1234-1235", + description="test", + expected_status=str(FixStatus.SYSTEM_STILL_VULNERABLE), + affected_packages=["pkg1"], + plan=[ + FixPlanAttachStep( + data=AttachData( + reason="required-pro-service", + required_service="esm-infra", + source_packages=["pkg1"], + ), + order=3, + ), + FixPlanEnableStep( + data=EnableData( + service="esm-infra", + source_packages=["pkg1"], + ), + order=4, + ), + FixPlanAptUpgradeStep( + data=AptUpgradeData( + binary_packages=["bin1"], + source_packages=["pkg1"], + pocket="esm-infra", + ), + order=5, + ), + ], + warnings=[ + FixPlanWarningFailUpdatingESMCache( + data=FailUpdatingESMCacheData( + title=expected_error_msg.msg, + code=expected_error_msg.name, + ), + order=1, + ), + FixPlanWarningPackageCannotBeInstalled( + data=PackageCannotBeInstalledData( + binary_package="bin2", + source_package="pkg1", + binary_package_version="1.2~esm1", + pocket="esm-infra", + related_source_packages=["pkg1"], + ), + order=2, + ), + ], + error=None, + additional_data=AdditionalData(), + ) assert expected_plan == fix_plan_cve( issue_id="cve-1234-1235", cfg=mock.MagicMock() ) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_services_dependencies_v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_services_dependencies_v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_services_dependencies_v1.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_services_dependencies_v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,211 @@ +import mock + +from uaclient import messages +from uaclient.api.u.pro.services.dependencies.v1 import ( + DependenciesResult, + Reason, + ServiceWithDependencies, + ServiceWithReason, + _dependencies, +) + + +class TestServicesDependenciesV1: + def test_dependencies(self): + assert _dependencies(cfg=mock.MagicMock()) == DependenciesResult( + services=[ + ServiceWithDependencies( + name="anbox-cloud", incompatible_with=[], depends_on=[] + ), + ServiceWithDependencies( + name="cc-eal", incompatible_with=[], depends_on=[] + ), + ServiceWithDependencies( + name="cis", incompatible_with=[], depends_on=[] + ), + ServiceWithDependencies( + name="esm-apps", incompatible_with=[], depends_on=[] + ), + ServiceWithDependencies( + name="esm-infra", incompatible_with=[], depends_on=[] + ), + ServiceWithDependencies( + name="fips", + incompatible_with=[ + ServiceWithReason( + name="livepatch", + reason=Reason( + code=messages.LIVEPATCH_INVALIDATES_FIPS.name, + title=messages.LIVEPATCH_INVALIDATES_FIPS.msg, + ), + ), + ServiceWithReason( + name="fips-updates", + reason=Reason( + code=messages.FIPS_UPDATES_INVALIDATES_FIPS.name, # noqa: E501 + title=messages.FIPS_UPDATES_INVALIDATES_FIPS.msg, # noqa: E501 + ), + ), + ServiceWithReason( + name="realtime-kernel", + reason=Reason( + code=messages.REALTIME_FIPS_INCOMPATIBLE.name, + title=messages.REALTIME_FIPS_INCOMPATIBLE.msg, + ), + ), + ], + depends_on=[], + ), + ServiceWithDependencies( + name="fips-updates", + incompatible_with=[ + ServiceWithReason( + name="fips", + reason=Reason( + code=messages.FIPS_INVALIDATES_FIPS_UPDATES.name, # noqa: E501 + title=messages.FIPS_INVALIDATES_FIPS_UPDATES.msg, # noqa: E501 + ), + ), + ServiceWithReason( + name="realtime-kernel", + reason=Reason( + code=messages.REALTIME_FIPS_UPDATES_INCOMPATIBLE.name, # noqa: E501 + title=messages.REALTIME_FIPS_UPDATES_INCOMPATIBLE.msg, # noqa: E501 + ), + ), + ], + depends_on=[], + ), + ServiceWithDependencies( + name="fips-preview", + incompatible_with=[ + ServiceWithReason( + name="livepatch", + reason=Reason( + code=messages.LIVEPATCH_INVALIDATES_FIPS.name, + title=messages.LIVEPATCH_INVALIDATES_FIPS.msg, + ), + ), + ServiceWithReason( + name="fips-updates", + reason=Reason( + code=messages.FIPS_UPDATES_INVALIDATES_FIPS.name, # noqa: E501 + title=messages.FIPS_UPDATES_INVALIDATES_FIPS.msg, # noqa: E501 + ), + ), + ServiceWithReason( + name="realtime-kernel", + reason=Reason( + code=messages.REALTIME_FIPS_INCOMPATIBLE.name, + title=messages.REALTIME_FIPS_INCOMPATIBLE.msg, + ), + ), + ServiceWithReason( + name="fips", + reason=Reason( + code=messages.FIPS_INVALIDATES_FIPS_UPDATES.name, # noqa: E501 + title=messages.FIPS_INVALIDATES_FIPS_UPDATES.msg, # noqa: E501 + ), + ), + ], + depends_on=[], + ), + ServiceWithDependencies( + name="landscape", incompatible_with=[], depends_on=[] + ), + ServiceWithDependencies( + name="livepatch", + incompatible_with=[ + ServiceWithReason( + name="fips", + reason=Reason( + code=messages.LIVEPATCH_INVALIDATES_FIPS.name, + title=messages.LIVEPATCH_INVALIDATES_FIPS.msg, + ), + ), + ServiceWithReason( + name="realtime-kernel", + reason=Reason( + code=messages.REALTIME_LIVEPATCH_INCOMPATIBLE.name, # noqa: E501 + title=messages.REALTIME_LIVEPATCH_INCOMPATIBLE.msg, # noqa: E501 + ), + ), + ], + depends_on=[], + ), + ServiceWithDependencies( + name="realtime-kernel", + incompatible_with=[ + ServiceWithReason( + name="fips", + reason=Reason( + code=messages.REALTIME_FIPS_INCOMPATIBLE.name, + title=messages.REALTIME_FIPS_INCOMPATIBLE.msg, + ), + ), + ServiceWithReason( + name="fips-updates", + reason=Reason( + code=messages.REALTIME_FIPS_UPDATES_INCOMPATIBLE.name, # noqa: E501 + title=messages.REALTIME_FIPS_UPDATES_INCOMPATIBLE.msg, # noqa: E501 + ), + ), + ServiceWithReason( + name="livepatch", + reason=Reason( + code=messages.REALTIME_LIVEPATCH_INCOMPATIBLE.name, # noqa: E501 + title=messages.REALTIME_LIVEPATCH_INCOMPATIBLE.msg, # noqa: E501 + ), + ), + ], + depends_on=[], + ), + ServiceWithDependencies( + name="ros", + incompatible_with=[], + depends_on=[ + ServiceWithReason( + name="esm-infra", + reason=Reason( + code=messages.ROS_REQUIRES_ESM.name, + title=messages.ROS_REQUIRES_ESM.msg, + ), + ), + ServiceWithReason( + name="esm-apps", + reason=Reason( + code=messages.ROS_REQUIRES_ESM.name, + title=messages.ROS_REQUIRES_ESM.msg, + ), + ), + ], + ), + ServiceWithDependencies( + name="ros-updates", + incompatible_with=[], + depends_on=[ + ServiceWithReason( + name="esm-infra", + reason=Reason( + code=messages.ROS_REQUIRES_ESM.name, + title=messages.ROS_REQUIRES_ESM.msg, + ), + ), + ServiceWithReason( + name="esm-apps", + reason=Reason( + code=messages.ROS_REQUIRES_ESM.name, + title=messages.ROS_REQUIRES_ESM.msg, + ), + ), + ServiceWithReason( + name="ros", + reason=Reason( + code=messages.ROS_UPDATES_REQUIRES_ROS.name, + title=messages.ROS_UPDATES_REQUIRES_ROS.msg, + ), + ), + ], + ), + ] + ) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_services_disable_v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_services_disable_v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_services_disable_v1.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_services_disable_v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,166 @@ +import mock +import pytest + +from uaclient.api import exceptions +from uaclient.api.u.pro.services.disable.v1 import ( + DisableOptions, + DisableResult, + _disable, +) +from uaclient.testing.helpers import does_not_raise + +M_PATH = "uaclient.api.u.pro.services.disable.v1." + + +class TestDisable: + @pytest.mark.parametrize( + [ + "options", + "we_are_currently_root", + "is_attached", + "enabled_services_names_before", + "enabled_services_names_after", + "disable_result", + "expected_raises", + "expected_result", + ], + [ + # not root + ( + DisableOptions(service="s1"), + False, + False, + None, + None, + None, + pytest.raises(exceptions.NonRootUserError), + None, + ), + # not attached + ( + DisableOptions(service="s1"), + True, + False, + None, + None, + None, + pytest.raises(exceptions.UnattachedError), + None, + ), + # generic disable failure + ( + DisableOptions(service="s1"), + True, + True, + ["s1"], + None, + (False, None), + pytest.raises(exceptions.EntitlementNotDisabledError), + None, + ), + # success + ( + DisableOptions(service="s1"), + True, + True, + ["s1"], + [], + (True, None), + does_not_raise(), + DisableResult( + disabled=["s1"], + ), + ), + # success already disabled + ( + DisableOptions(service="s1"), + True, + True, + [], + None, + None, + does_not_raise(), + DisableResult( + disabled=[], + ), + ), + # success with additional disablements + ( + DisableOptions(service="s1"), + True, + True, + ["s1", "s2", "s3"], + ["s2"], + (True, None), + does_not_raise(), + DisableResult( + disabled=["s1", "s3"], + ), + ), + ], + ) + @mock.patch(M_PATH + "status.status") + @mock.patch(M_PATH + "lock.clear_lock_file_if_present") + @mock.patch(M_PATH + "lock.RetryLock") + @mock.patch(M_PATH + "entitlements.entitlement_factory") + @mock.patch(M_PATH + "_enabled_services_names") + @mock.patch(M_PATH + "_is_attached") + @mock.patch(M_PATH + "util.we_are_currently_root") + def test_disable( + self, + m_we_are_currently_root, + m_is_attached, + m_enabled_services_names, + m_entitlement_factory, + m_spin_lock, + m_clear_lock_file_if_present, + m_status, + options, + we_are_currently_root, + is_attached, + enabled_services_names_before, + enabled_services_names_after, + disable_result, + expected_raises, + expected_result, + FakeConfig, + ): + m_we_are_currently_root.return_value = we_are_currently_root + m_is_attached.return_value = mock.MagicMock(is_attached=is_attached) + m_enabled_services_names.side_effect = [ + enabled_services_names_before, + enabled_services_names_after, + ] + m_ent_class = m_entitlement_factory.return_value + m_ent = m_ent_class.return_value + m_ent_variant = m_ent.enabled_variant + m_ent_variant.disable.return_value = disable_result + + cfg = FakeConfig() + + actual_result = None + with expected_raises: + actual_result = _disable( + options, cfg, progress_object=mock.MagicMock() + ) + + assert actual_result == expected_result + + if expected_result is not None and len(expected_result.disabled) > 0: + assert m_entitlement_factory.call_args_list == [ + mock.call( + cfg=cfg, + name=options.service, + ) + ] + assert m_ent_class.call_args_list == [ + mock.call( + cfg, + assume_yes=True, + called_name=options.service, + purge=options.purge, + ) + ] + assert m_ent_variant.disable.call_args_list == [ + mock.call(mock.ANY) + ] diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_services_enable_v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_services_enable_v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_services_enable_v1.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_services_enable_v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,241 @@ +import mock +import pytest + +from uaclient.api import exceptions +from uaclient.api.u.pro.services.enable.v1 import ( + EnableOptions, + EnableResult, + _enable, +) +from uaclient.testing.helpers import does_not_raise + +M_PATH = "uaclient.api.u.pro.services.enable.v1." + + +class TestEnable: + @pytest.mark.parametrize( + [ + "options", + "we_are_currently_root", + "is_attached", + "enabled_services_names_before", + "enabled_services_names_after", + "enable_result", + "messaging_ops", + "reboot_required", + "expected_raises", + "expected_result", + ], + [ + # not root + ( + EnableOptions(service="s1"), + False, + False, + None, + None, + None, + None, + None, + pytest.raises(exceptions.NonRootUserError), + None, + ), + # not attached + ( + EnableOptions(service="s1"), + True, + False, + None, + None, + None, + None, + None, + pytest.raises(exceptions.UnattachedError), + None, + ), + # landscape fail + ( + EnableOptions(service="landscape"), + True, + True, + None, + None, + None, + None, + None, + pytest.raises(exceptions.NotSupported), + None, + ), + # generic enable failure + ( + EnableOptions(service="s1"), + True, + True, + [], + None, + (False, None), + None, + None, + pytest.raises(exceptions.EntitlementNotEnabledError), + None, + ), + # success + ( + EnableOptions(service="s1"), + True, + True, + [], + ["s1"], + (True, None), + {}, + False, + does_not_raise(), + EnableResult( + enabled=["s1"], + disabled=[], + reboot_required=False, + messages=[], + ), + ), + # success already enabled + ( + EnableOptions(service="s1"), + True, + True, + ["s1"], + None, + None, + None, + None, + does_not_raise(), + EnableResult( + enabled=[], + disabled=[], + reboot_required=False, + messages=[], + ), + ), + # success with reboot required + ( + EnableOptions(service="s1"), + True, + True, + [], + ["s1"], + (True, None), + {}, + True, + does_not_raise(), + EnableResult( + enabled=["s1"], + disabled=[], + reboot_required=True, + messages=[], + ), + ), + # success with post enable messages + ( + EnableOptions(service="s1"), + True, + True, + [], + ["s1"], + (True, None), + {"post_enable": ["one", "two", 3]}, + False, + does_not_raise(), + EnableResult( + enabled=["s1"], + disabled=[], + reboot_required=False, + messages=["one", "two"], + ), + ), + # success with additional enablements and disablements + ( + EnableOptions(service="s1"), + True, + True, + ["s2"], + ["s1", "s3"], + (True, None), + {}, + False, + does_not_raise(), + EnableResult( + enabled=["s1", "s3"], + disabled=["s2"], + reboot_required=False, + messages=[], + ), + ), + ], + ) + @mock.patch(M_PATH + "status.status") + @mock.patch(M_PATH + "lock.clear_lock_file_if_present") + @mock.patch(M_PATH + "lock.RetryLock") + @mock.patch(M_PATH + "entitlements.entitlement_factory") + @mock.patch(M_PATH + "_enabled_services_names") + @mock.patch(M_PATH + "_is_attached") + @mock.patch(M_PATH + "util.we_are_currently_root") + def test_enable( + self, + m_we_are_currently_root, + m_is_attached, + m_enabled_services_names, + m_entitlement_factory, + m_spin_lock, + m_clear_lock_file_if_present, + m_status, + options, + is_attached, + we_are_currently_root, + enabled_services_names_before, + enabled_services_names_after, + enable_result, + messaging_ops, + reboot_required, + expected_raises, + expected_result, + FakeConfig, + ): + m_we_are_currently_root.return_value = we_are_currently_root + m_is_attached.return_value = mock.MagicMock(is_attached=is_attached) + m_enabled_services_names.side_effect = [ + enabled_services_names_before, + enabled_services_names_after, + ] + m_ent_class = m_entitlement_factory.return_value + m_ent = m_ent_class.return_value + m_ent.enable.return_value = enable_result + m_ent.messaging = messaging_ops + m_ent._check_for_reboot.return_value = reboot_required + + cfg = FakeConfig() + + actual_result = None + with expected_raises: + actual_result = _enable( + options, cfg, progress_object=mock.MagicMock() + ) + + assert actual_result == expected_result + + if expected_result is not None and len(expected_result.enabled) > 0: + assert m_entitlement_factory.call_args_list == [ + mock.call( + cfg=cfg, + name=options.service, + variant=options.variant or "", + ) + ] + assert m_ent_class.call_args_list == [ + mock.call( + cfg, + assume_yes=True, + allow_beta=True, + called_name=options.service, + access_only=options.access_only, + ) + ] + assert m_ent.enable.call_args_list == [mock.call(mock.ANY)] diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_status_is_attached_v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_status_is_attached_v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/tests/test_api_u_pro_status_is_attached_v1.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/tests/test_api_u_pro_status_is_attached_v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,38 @@ +import datetime + +import pytest + +from uaclient.api.u.pro.status.is_attached.v1 import ( + ContractExpiryStatus, + _is_attached, +) + + +class TestGetContractExpiryStatus: + @pytest.mark.parametrize( + "contract_remaining_days,expected_status", + ( + (21, ContractExpiryStatus.ACTIVE), + (20, ContractExpiryStatus.ACTIVE_EXPIRED_SOON), + (-1, ContractExpiryStatus.EXPIRED_GRACE_PERIOD), + (-20, ContractExpiryStatus.EXPIRED), + ), + ) + def test_contract_expiry_status_based_on_remaining_days( + self, contract_remaining_days, expected_status, FakeConfig + ): + """Return a tuple of ContractExpiryStatus and remaining_days""" + now = datetime.datetime.utcnow() + expire_date = now + datetime.timedelta(days=contract_remaining_days) + cfg = FakeConfig.for_attached_machine() + m_token = cfg.machine_token + m_token["machineTokenInfo"]["contractInfo"][ + "effectiveTo" + ] = expire_date + + is_attached_info = _is_attached(cfg) + assert is_attached_info.is_attached + assert expected_status.value == is_attached_info.contract_status + assert ( + contract_remaining_days == is_attached_info.contract_remaining_days + ) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/attach/auto/full_auto_attach/v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/attach/auto/full_auto_attach/v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/attach/auto/full_auto_attach/v1.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/attach/auto/full_auto_attach/v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -79,7 +79,6 @@ ) -> FullAutoAttachResult: try: with lock.RetryLock( - cfg=cfg, lock_holder="pro.api.u.pro.attach.auto.full_auto_attach.v1", ): ret = _full_auto_attach_in_lock(options, cfg, mode=mode) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/attach/magic/initiate/v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/attach/magic/initiate/v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/attach/magic/initiate/v1.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/attach/magic/initiate/v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,3 +1,4 @@ +from uaclient import secret_manager from uaclient.api.api import APIEndpoint from uaclient.api.data_types import AdditionalInfo from uaclient.config import UAConfig @@ -38,7 +39,8 @@ def _initiate(cfg: UAConfig) -> MagicAttachInitiateResult: contract = UAContractClient(cfg) initiate_resp = contract.new_magic_attach_token() - + secret_manager.secrets.add_secret(initiate_resp["token"]) + secret_manager.secrets.add_secret(initiate_resp["userCode"]) return MagicAttachInitiateResult( user_code=initiate_resp["userCode"], token=initiate_resp["token"], diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/attach/magic/wait/v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/attach/magic/wait/v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/attach/magic/wait/v1.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/attach/magic/wait/v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,6 +1,6 @@ import time -from uaclient import exceptions +from uaclient import exceptions, secret_manager from uaclient.api.api import APIEndpoint from uaclient.api.data_types import AdditionalInfo from uaclient.config import UAConfig @@ -66,6 +66,7 @@ num_connection_errors = 0 wait_time = 10 + secret_manager.secrets.add_secret(options.magic_token) while num_attempts < MAXIMUM_ATTEMPTS: wait_resp = None diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/attach/token/full_token_attach/v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/attach/token/full_token_attach/v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/attach/token/full_token_attach/v1.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/attach/token/full_token_attach/v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,127 @@ +from typing import Dict, List # noqa: F401 + +from uaclient import exceptions, lock, util +from uaclient.actions import attach_with_token +from uaclient.api.api import APIEndpoint +from uaclient.api.data_types import AdditionalInfo, ErrorWarningObject +from uaclient.api.u.pro.security.status.reboot_required.v1 import ( + _reboot_required, +) +from uaclient.api.u.pro.status.enabled_services.v1 import _enabled_services +from uaclient.api.u.pro.status.is_attached.v1 import _is_attached +from uaclient.config import UAConfig +from uaclient.data_types import ( + BoolDataValue, + DataObject, + Field, + StringDataValue, + data_list, +) + + +class FullTokenAttachOptions(DataObject): + fields = [ + Field("token", StringDataValue), + Field("auto_enable_services", BoolDataValue, False), + ] + + def __init__(self, token: str, auto_enable_services: bool = True): + self.token = token + self.auto_enable_services = auto_enable_services + + +class FullTokenAttachResult(DataObject, AdditionalInfo): + fields = [ + Field("enabled", data_list(StringDataValue)), + Field("reboot_required", BoolDataValue), + ] + + def __init__( + self, + enabled: List[str], + reboot_required: bool, + ): + self.enabled = enabled + self.reboot_required = reboot_required + + +def _full_token_attach( + options: FullTokenAttachOptions, cfg: UAConfig +) -> FullTokenAttachResult: + if not util.we_are_currently_root(): + raise exceptions.NonRootUserError + + if _is_attached(cfg).is_attached: + return FullTokenAttachResult( + enabled=[], + reboot_required=False, + ) + + try: + with lock.RetryLock( + lock_holder="pro.api.u.pro.attach.token.full_token_attach.v1", + ): + ret = _full_token_attach_in_lock(options, cfg) + except Exception as e: + lock.clear_lock_file_if_present() + raise e + return ret + + +def _full_token_attach_in_lock( + options: FullTokenAttachOptions, cfg: UAConfig +) -> FullTokenAttachResult: + failed_services = [] # type: List[Dict[str, str]] + + auto_enable_services = options.auto_enable_services + if auto_enable_services is None: + auto_enable_services = True + + try: + attach_with_token( + cfg, + options.token, + allow_enable=auto_enable_services, + silent=True, + ) + except ( + exceptions.AttachFailureUnknownError, + exceptions.AttachFailureDefaultServices, + ) as exc: + failed_services = exc.additional_info.get("services", []) + + enabled_services = [ + service.name for service in _enabled_services(cfg).enabled_services + ] + reboot_required_result = _reboot_required(cfg) + + result = FullTokenAttachResult( + enabled=enabled_services, + reboot_required=reboot_required_result.reboot_required == "yes", + ) + + if failed_services: + result.warnings = [ + ErrorWarningObject( + title=service["title"], + code=service["code"], + meta={"service": service["name"]}, + ) + for service in failed_services + ] + + return result + + +def full_token_attach( + options: FullTokenAttachOptions, +) -> FullTokenAttachResult: + return _full_token_attach(options, UAConfig()) + + +endpoint = APIEndpoint( + version="v1", + name="FullTokenAttach", + fn=_full_token_attach, + options_cls=FullTokenAttachOptions, +) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/detach/v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/detach/v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/detach/v1.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/detach/v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,123 @@ +from typing import List + +from uaclient import ( + daemon, + entitlements, + exceptions, + lock, + messages, + timer, + util, +) +from uaclient.api import ProgressWrapper +from uaclient.api.api import APIEndpoint +from uaclient.api.data_types import AdditionalInfo, ErrorWarningObject +from uaclient.api.u.pro.security.status.reboot_required.v1 import ( + _reboot_required, +) +from uaclient.api.u.pro.status.is_attached.v1 import _is_attached +from uaclient.config import UAConfig +from uaclient.data_types import ( + BoolDataValue, + DataObject, + Field, + StringDataValue, + data_list, +) +from uaclient.files import state_files +from uaclient.timer.update_messaging import update_motd_messages + + +class DetachResult(DataObject, AdditionalInfo): + fields = [ + Field("disabled", data_list(StringDataValue)), + Field("reboot_required", BoolDataValue), + ] + + def __init__(self, disabled: List[str], reboot_required: bool): + self.disabled = disabled + self.reboot_required = reboot_required + + +def detach() -> DetachResult: + return _detach(UAConfig()) + + +def _detach(cfg: UAConfig) -> DetachResult: + if not util.we_are_currently_root(): + raise exceptions.NonRootUserError + + try: + with lock.RetryLock( + lock_holder="pro.api.u.pro.detach.v1", + ): + ret = _detach_in_lock(cfg) + except Exception as e: + lock.clear_lock_file_if_present() + raise e + return ret + + +def _detach_in_lock(cfg: UAConfig) -> DetachResult: + if not _is_attached(cfg).is_attached: + return DetachResult( + disabled=[], + reboot_required=False, + ) + + disabled = [] + warnings = [] # type: List[ErrorWarningObject] + for ent_name in entitlements.entitlements_disable_order(cfg): + try: + ent_cls = entitlements.entitlement_factory(cfg=cfg, name=ent_name) + except exceptions.EntitlementNotFoundError: + continue + + ent = ent_cls(cfg=cfg, assume_yes=True) + # For detach, we should not consider that a service + # cannot be disabled because of dependent services, + # since we are going to disable all of them anyway + can_disable, _ = ent.can_disable(ignore_dependent_services=True) + if can_disable: + ret, reason = ent.disable(ProgressWrapper()) + if not ret: + if reason and reason.message: + msg = reason.message.msg + code = reason.message.name + else: + msg = messages.DISABLE_FAILED_TMPL.format(title=ent_name) + code = "" + + warnings.append( + ErrorWarningObject( + title=msg, + code=code, + meta={"service": ent_name}, + ) + ) + else: + disabled.append(ent_name) + + state_files.delete_state_files() + cfg.machine_token_file.delete() + update_motd_messages(cfg) + daemon.start() + timer.stop() + + reboot_required_result = _reboot_required(cfg) + + result = DetachResult( + disabled=sorted(disabled), + reboot_required=reboot_required_result.reboot_required == "yes", + ) + result.warnings = warnings + + return result + + +endpoint = APIEndpoint( + version="v1", + name="Detach", + fn=_detach, + options_cls=None, +) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/security/fix/_common/plan/v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/security/fix/_common/plan/v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/security/fix/_common/plan/v1.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/security/fix/_common/plan/v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,6 +1,7 @@ import enum import re from collections import defaultdict +from datetime import datetime, timezone from typing import Any, Dict, List, NamedTuple, Optional, Tuple from uaclient import apt, exceptions, messages @@ -21,9 +22,11 @@ query_installed_source_pkg_versions, ) from uaclient.api.u.pro.status.enabled_services.v1 import _enabled_services -from uaclient.api.u.pro.status.is_attached.v1 import _is_attached +from uaclient.api.u.pro.status.is_attached.v1 import ( + ContractExpiryStatus, + _is_attached, +) from uaclient.config import UAConfig -from uaclient.contract import ContractExpiryStatus, get_contract_expiry_status from uaclient.data_types import ( DataObject, Field, @@ -71,6 +74,7 @@ class FixWarningType(enum.Enum): PACKAGE_CANNOT_BE_INSTALLED = "package-cannot-be-installed" SECURITY_ISSUE_NOT_FIXED = "security-issue-not-fixed" + FAIL_UPDATING_ESM_CACHE = "fail-updating-esm-cache" class FixPlanStep(DataObject): @@ -311,6 +315,32 @@ self.data = data +class FailUpdatingESMCacheData(DataObject): + fields = [ + Field("title", StringDataValue), + Field("code", StringDataValue), + ] + + def __init__(self, *, title: str, code: str): + self.title = title + self.code = code + + +class FixPlanWarningFailUpdatingESMCache(FixPlanWarning): + fields = [ + Field("warning_type", StringDataValue), + Field("order", IntDataValue), + Field("data", FailUpdatingESMCacheData), + ] + + def __init__(self, *, order: int, data: FailUpdatingESMCacheData): + super().__init__( + warning_type=FixWarningType.FAIL_UPDATING_ESM_CACHE.value, + order=order, + ) + self.data = data + + class FixPlanError(DataObject): fields = [ Field("msg", StringDataValue), @@ -456,11 +486,16 @@ order=self.order, data=SecurityIssueNotFixedData.from_dict(data), ) - else: + elif warning_type == FixWarningType.PACKAGE_CANNOT_BE_INSTALLED: fix_warning = FixPlanWarningPackageCannotBeInstalled( order=self.order, data=PackageCannotBeInstalledData.from_dict(data), ) + else: + fix_warning = FixPlanWarningFailUpdatingESMCache( + order=self.order, + data=FailUpdatingESMCacheData.from_dict(data), + ) self.fix_warnings.append(fix_warning) self.order += 1 @@ -564,15 +599,12 @@ def _get_upgradable_pkgs( binary_pkgs: List[BinaryPackageFix], - pocket: str, + check_esm_cache: bool, ) -> Tuple[List[str], List[UnfixedPackage]]: upgrade_pkgs = [] unfixed_pkgs = [] for binary_pkg in sorted(binary_pkgs): - check_esm_cache = ( - pocket != messages.SECURITY_UBUNTU_STANDARD_UPDATES_POCKET - ) candidate_version = apt.get_pkg_candidate_version( binary_pkg.binary_pkg, check_esm_cache=check_esm_cache ) @@ -715,9 +747,9 @@ ) additional_data = { "associated_cves": [] if not usn.cves_ids else usn.cves_ids, - "associated_launchpad_bugs": [] - if not usn.references - else usn.references, + "associated_launchpad_bugs": ( + [] if not usn.references else usn.references + ), } target_usn_plan = _generate_fix_plan( @@ -740,9 +772,9 @@ ) additional_data = { "associated_cves": [] if not usn.cves_ids else usn.cves_ids, - "associated_launchpad_bugs": [] - if not usn.references - else usn.references, + "associated_launchpad_bugs": ( + [] if not usn.references else usn.references + ), } related_usns_plan.append( @@ -799,6 +831,28 @@ return pocket +def _should_update_esm_cache( + check_esm_cache: bool, + esm_cache_updated: bool, + cfg: UAConfig, +) -> bool: + if ( + check_esm_cache + and not esm_cache_updated + and not _is_attached(cfg).is_attached + ): + last_apt_update = apt.get_apt_cache_datetime() + if last_apt_update is None: + return True + + now = datetime.now(timezone.utc) + time_since_update = now - last_apt_update + if time_since_update.days > 2: + return True + + return False + + def _generate_fix_plan( *, issue_id: str, @@ -811,6 +865,7 @@ ) -> FixPlanResult: count = len(affected_pkg_status) src_pocket_pkgs = defaultdict(list) + esm_cache_updated = False fix_plan = get_fix_plan( title=issue_id, @@ -878,7 +933,30 @@ ) continue - upgrade_pkgs, unfixed_pkgs = _get_upgradable_pkgs(binary_pkgs, pocket) + check_esm_cache = ( + pocket != messages.SECURITY_UBUNTU_STANDARD_UPDATES_POCKET + ) + + if _should_update_esm_cache(check_esm_cache, esm_cache_updated, cfg): + try: + apt.update_esm_caches(cfg) + esm_cache_updated = True + except Exception as e: + error_msg = messages.E_UPDATING_ESM_CACHE.format( + error=getattr(e, "msg", str(e)) + ) + + fix_plan.register_warning( + warning_type=FixWarningType.FAIL_UPDATING_ESM_CACHE, + data={ + "title": error_msg.msg, + "code": error_msg.name, + }, + ) + + upgrade_pkgs, unfixed_pkgs = _get_upgradable_pkgs( + binary_pkgs, check_esm_cache + ) if unfixed_pkgs: for unfixed_pkg in unfixed_pkgs: @@ -909,8 +987,8 @@ }, ) else: - contract_expiry_status, _ = get_contract_expiry_status(cfg) - if contract_expiry_status != ContractExpiryStatus.ACTIVE: + contract_expiry_status = _is_attached(cfg).contract_status + if contract_expiry_status != ContractExpiryStatus.ACTIVE.value: fix_plan.register_step( operation=FixStepType.ATTACH, data={ diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/security/status/reboot_required/v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/security/status/reboot_required/v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/security/status/reboot_required/v1.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/security/status/reboot_required/v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -149,12 +149,16 @@ return RebootRequiredResult( reboot_required=reboot_status.value, reboot_required_packages=RebootRequiredPkgs( - standard_packages=reboot_required_pkgs.standard_packages - if reboot_required_pkgs - else None, - kernel_packages=reboot_required_pkgs.kernel_packages - if reboot_required_pkgs - else None, + standard_packages=( + reboot_required_pkgs.standard_packages + if reboot_required_pkgs + else None + ), + kernel_packages=( + reboot_required_pkgs.kernel_packages + if reboot_required_pkgs + else None + ), ), livepatch_enabled_and_kernel_patched=livepatch_enabled_and_kernel_patched, # noqa livepatch_enabled=livepatch_enabled, diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/services/dependencies/v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/services/dependencies/v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/services/dependencies/v1.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/services/dependencies/v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,108 @@ +import logging +from typing import List + +from uaclient import entitlements, util +from uaclient.api.api import APIEndpoint +from uaclient.api.data_types import AdditionalInfo +from uaclient.config import UAConfig +from uaclient.data_types import DataObject, Field, StringDataValue, data_list + +LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) + + +class Reason(DataObject): + fields = [ + Field("code", StringDataValue), + Field("title", StringDataValue), + ] + + def __init__(self, *, code: str, title: str): + self.code = code + self.title = title + + +class ServiceWithReason(DataObject): + fields = [ + Field("name", StringDataValue), + Field("reason", Reason), + ] + + def __init__(self, *, name: str, reason: Reason): + self.name = name + self.reason = reason + + +class ServiceWithDependencies(DataObject): + fields = [ + Field("name", StringDataValue), + Field("incompatible_with", data_list(ServiceWithReason)), + Field("depends_on", data_list(ServiceWithReason)), + ] + + def __init__( + self, + *, + name: str, + incompatible_with: List[ServiceWithReason], + depends_on: List[ServiceWithReason] + ): + self.name = name + self.incompatible_with = incompatible_with + self.depends_on = depends_on + + +class DependenciesResult(DataObject, AdditionalInfo): + fields = [ + Field("services", data_list(ServiceWithDependencies)), + ] + + def __init__(self, *, services: List[ServiceWithDependencies]): + self.services = services + + +def dependencies() -> DependenciesResult: + return _dependencies(UAConfig()) + + +def _dependencies(cfg: UAConfig) -> DependenciesResult: + services = [] + for ent_cls in entitlements.ENTITLEMENT_CLASSES: + ent = ent_cls(cfg) + incompatible_with = [] + depends_on = [] + for ent_with_reason in ent.incompatible_services: + incompatible_with.append( + ServiceWithReason( + name=ent_with_reason.entitlement.name, + reason=Reason( + code=ent_with_reason.named_msg.name, + title=ent_with_reason.named_msg.msg, + ), + ) + ) + for ent_with_reason in ent.required_services: + depends_on.append( + ServiceWithReason( + name=ent_with_reason.entitlement.name, + reason=Reason( + code=ent_with_reason.named_msg.name, + title=ent_with_reason.named_msg.msg, + ), + ) + ) + services.append( + ServiceWithDependencies( + name=ent_cls.name, + incompatible_with=incompatible_with, + depends_on=depends_on, + ) + ) + return DependenciesResult(services=services) + + +endpoint = APIEndpoint( + version="v1", + name="ServiceDependencies", + fn=_dependencies, + options_cls=None, +) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/services/disable/v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/services/disable/v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/services/disable/v1.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/services/disable/v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,132 @@ +import logging +from typing import List, Optional + +from uaclient import entitlements, lock, messages, status, util +from uaclient.api import AbstractProgress, ProgressWrapper, exceptions +from uaclient.api.api import APIEndpoint +from uaclient.api.data_types import AdditionalInfo +from uaclient.api.u.pro.status.enabled_services.v1 import _enabled_services +from uaclient.api.u.pro.status.is_attached.v1 import _is_attached +from uaclient.config import UAConfig +from uaclient.data_types import ( + BoolDataValue, + DataObject, + Field, + StringDataValue, + data_list, +) + +LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) + + +class DisableOptions(DataObject): + fields = [ + Field("service", StringDataValue), + Field("purge", BoolDataValue, False), + ] + + def __init__(self, *, service: str, purge: bool = False): + self.service = service + self.purge = purge + + +class DisableResult(DataObject, AdditionalInfo): + fields = [ + Field("disabled", data_list(StringDataValue)), + ] + + def __init__(self, *, disabled: List[str]): + self.disabled = disabled + + +def _enabled_services_names(cfg: UAConfig) -> List[str]: + return [s.name for s in _enabled_services(cfg).enabled_services] + + +def disable( + options: DisableOptions, progress_object: Optional[AbstractProgress] = None +) -> DisableResult: + return _disable(options, UAConfig(), progress_object=progress_object) + + +def _disable( + options: DisableOptions, + cfg: UAConfig, + progress_object: Optional[AbstractProgress] = None, +) -> DisableResult: + progress = ProgressWrapper(progress_object) + + if not util.we_are_currently_root(): + raise exceptions.NonRootUserError() + + if not _is_attached(cfg).is_attached: + raise exceptions.UnattachedError() + + enabled_services_before = _enabled_services_names(cfg) + + ent_cls = entitlements.entitlement_factory(cfg=cfg, name=options.service) + + # Do this after getting the class so that the factory can raise an + # exception for invalid service names + if options.service not in enabled_services_before: + # nothing to do + return DisableResult( + disabled=[], + ) + + entitlement = ent_cls( + cfg, + assume_yes=True, + called_name=options.service, + purge=options.purge, + ) + variant = entitlement.enabled_variant + if variant is not None: + entitlement = variant + + progress.total_steps = entitlement.calculate_total_disable_steps() + + success = False + fail_reason = None + + try: + with lock.RetryLock( + lock_holder="u.pro.services.disable.v1", + ): + success, fail_reason = entitlement.disable(progress) + except Exception as e: + lock.clear_lock_file_if_present() + raise e + + if not success: + if fail_reason is not None and fail_reason.message is not None: + reason = fail_reason.message + else: + reason = messages.GENERIC_UNKNOWN_ISSUE + raise exceptions.EntitlementNotDisabledError( + service=options.service, reason=reason + ) + + enabled_services_after = _enabled_services_names(cfg) + + status.status(cfg=cfg) # Update the status cache + progress.finish() + + return DisableResult( + disabled=sorted( + list( + set(enabled_services_before).difference( + set(enabled_services_after) + ) + ) + ), + ) + + +endpoint = APIEndpoint( + version="v1", + name="DisableService", + fn=_disable, + options_cls=DisableOptions, + supports_progress=True, +) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/services/enable/v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/services/enable/v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/services/enable/v1.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/services/enable/v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,170 @@ +import logging +from typing import List, Optional + +from uaclient import entitlements, lock, messages, status, util +from uaclient.api import AbstractProgress, ProgressWrapper, exceptions +from uaclient.api.api import APIEndpoint +from uaclient.api.data_types import AdditionalInfo +from uaclient.api.u.pro.status.enabled_services.v1 import _enabled_services +from uaclient.api.u.pro.status.is_attached.v1 import _is_attached +from uaclient.config import UAConfig +from uaclient.data_types import ( + BoolDataValue, + DataObject, + Field, + StringDataValue, + data_list, +) + +LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) + + +class EnableOptions(DataObject): + fields = [ + Field("service", StringDataValue), + Field("variant", StringDataValue, False), + Field("access_only", BoolDataValue, False), + ] + + def __init__( + self, + *, + service: str, + variant: Optional[str] = None, + access_only: bool = False + ): + self.service = service + self.variant = variant + self.access_only = access_only + + +class EnableResult(DataObject, AdditionalInfo): + fields = [ + Field("enabled", data_list(StringDataValue)), + Field("disabled", data_list(StringDataValue)), + Field("reboot_required", BoolDataValue), + Field("messages", data_list(StringDataValue)), + ] + + def __init__( + self, + *, + enabled: List[str], + disabled: List[str], + reboot_required: bool, + messages: List[str] + ): + self.enabled = enabled + self.disabled = disabled + self.reboot_required = reboot_required + self.messages = messages + + +def _enabled_services_names(cfg: UAConfig) -> List[str]: + return [s.name for s in _enabled_services(cfg).enabled_services] + + +def enable( + options: EnableOptions, progress_object: Optional[AbstractProgress] = None +) -> EnableResult: + return _enable(options, UAConfig(), progress_object=progress_object) + + +def _enable( + options: EnableOptions, + cfg: UAConfig, + progress_object: Optional[AbstractProgress] = None, +) -> EnableResult: + progress = ProgressWrapper(progress_object) + + if not util.we_are_currently_root(): + raise exceptions.NonRootUserError() + + if not _is_attached(cfg).is_attached: + raise exceptions.UnattachedError() + + if options.service == "landscape": + raise exceptions.NotSupported() + + enabled_services_before = _enabled_services_names(cfg) + if options.service in enabled_services_before: + # nothing to do + return EnableResult( + enabled=[], + disabled=[], + reboot_required=False, + messages=[], + ) + + ent_cls = entitlements.entitlement_factory( + cfg=cfg, name=options.service, variant=options.variant or "" + ) + entitlement = ent_cls( + cfg, + assume_yes=True, + allow_beta=True, + called_name=options.service, + access_only=options.access_only, + ) + + progress.total_steps = entitlement.calculate_total_enable_steps() + + success = False + fail_reason = None + + try: + with lock.RetryLock( + lock_holder="u.pro.services.enable.v1", + ): + success, fail_reason = entitlement.enable(progress) + except Exception as e: + lock.clear_lock_file_if_present() + raise e + + if not success: + if fail_reason is not None and fail_reason.message is not None: + reason = fail_reason.message + else: + reason = messages.GENERIC_UNKNOWN_ISSUE + raise exceptions.EntitlementNotEnabledError( + service=options.service, reason=reason + ) + + enabled_services_after = _enabled_services_names(cfg) + + post_enable_messages = [ + msg + for msg in (entitlement.messaging.get("post_enable", []) or []) + if isinstance(msg, str) + ] + + status.status(cfg=cfg) # Update the status cache + progress.finish() + + return EnableResult( + enabled=sorted( + list( + set(enabled_services_after).difference( + set(enabled_services_before) + ) + ) + ), + disabled=sorted( + list( + set(enabled_services_before).difference( + set(enabled_services_after) + ) + ) + ), + reboot_required=entitlement._check_for_reboot(), + messages=post_enable_messages, + ) + + +endpoint = APIEndpoint( + version="v1", + name="EnableService", + fn=_enable, + options_cls=EnableOptions, + supports_progress=True, +) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/status/is_attached/v1.py ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/status/is_attached/v1.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/api/u/pro/status/is_attached/v1.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/api/u/pro/status/is_attached/v1.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,16 +1,76 @@ +import enum +from typing import Optional, Tuple + from uaclient.api.api import APIEndpoint from uaclient.api.data_types import AdditionalInfo from uaclient.config import UAConfig -from uaclient.data_types import BoolDataValue, DataObject, Field +from uaclient.data_types import ( + BoolDataValue, + DataObject, + Field, + IntDataValue, + StringDataValue, +) +from uaclient.defaults import ( + CONTRACT_EXPIRY_GRACE_PERIOD_DAYS, + CONTRACT_EXPIRY_PENDING_DAYS, +) class IsAttachedResult(DataObject, AdditionalInfo): fields = [ Field("is_attached", BoolDataValue), + Field("contract_status", StringDataValue, False), + Field("contract_remaining_days", IntDataValue), + Field("is_attached_and_contract_valid", BoolDataValue), ] - def __init__(self, *, is_attached: bool): + def __init__( + self, + *, + is_attached: bool, + contract_status: Optional[str], + contract_remaining_days: int, + is_attached_and_contract_valid: bool + ): self.is_attached = is_attached + self.contract_status = contract_status + self.contract_remaining_days = contract_remaining_days + self.is_attached_and_contract_valid = is_attached_and_contract_valid + + +@enum.unique +class ContractExpiryStatus(enum.Enum): + NONE = None + ACTIVE = "active" + ACTIVE_EXPIRED_SOON = "active-soon-to-expire" + EXPIRED_GRACE_PERIOD = "grace-period" + EXPIRED = "expired" + + +def _get_contract_expiry_status( + cfg: UAConfig, + is_machine_attached: bool, +) -> Tuple[ContractExpiryStatus, int]: + """Return a tuple [ContractExpiryStatus, num_days]""" + if not is_machine_attached: + return ContractExpiryStatus.NONE, 0 + + grace_period = CONTRACT_EXPIRY_GRACE_PERIOD_DAYS + pending_expiry = CONTRACT_EXPIRY_PENDING_DAYS + remaining_days = cfg.machine_token_file.contract_remaining_days + + # if unknown assume the worst + if remaining_days is None: + return ContractExpiryStatus.EXPIRED, -grace_period + + if 0 <= remaining_days <= pending_expiry: + return ContractExpiryStatus.ACTIVE_EXPIRED_SOON, remaining_days + elif -grace_period <= remaining_days < 0: + return ContractExpiryStatus.EXPIRED_GRACE_PERIOD, remaining_days + elif remaining_days < -grace_period: + return ContractExpiryStatus.EXPIRED, remaining_days + return ContractExpiryStatus.ACTIVE, remaining_days def is_attached() -> IsAttachedResult: @@ -18,7 +78,23 @@ def _is_attached(cfg: UAConfig) -> IsAttachedResult: - return IsAttachedResult(is_attached=bool(cfg.machine_token)) + is_machine_attached = bool(cfg.machine_token) + contract_status, remaining_days = _get_contract_expiry_status( + cfg, is_machine_attached + ) + is_attached_and_contract_valid = True + if ( + not is_machine_attached + or contract_status == ContractExpiryStatus.EXPIRED + ): + is_attached_and_contract_valid = False + + return IsAttachedResult( + is_attached=is_machine_attached, + contract_status=contract_status.value, + contract_remaining_days=remaining_days, + is_attached_and_contract_valid=is_attached_and_contract_valid, + ) endpoint = APIEndpoint( diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/apt.py ubuntu-advantage-tools-32~16.04/uaclient/apt.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/apt.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/apt.py 2024-04-23 13:37:02.000000000 +0000 @@ -13,8 +13,17 @@ import apt_pkg # type: ignore from apt.progress.base import AcquireProgress # type: ignore -from uaclient import event_logger, exceptions, gpg, messages, system, util +from uaclient import ( + event_logger, + exceptions, + gpg, + messages, + secret_manager, + system, + util, +) from uaclient.defaults import ESM_APT_ROOTDIR +from uaclient.files.state_files import status_cache_file APT_HELPER_TIMEOUT = 60.0 # 60 second timeout used for apt-helper call APT_AUTH_COMMENT = " # ubuntu-pro-client" @@ -69,6 +78,8 @@ os.path.join(ESM_APT_ROOTDIR, "var/lib/dpkg/status"), ], "folders": [ + os.path.join(ESM_APT_ROOTDIR, "etc/apt/apt.conf.d"), + os.path.join(ESM_APT_ROOTDIR, "etc/apt/preferences.d"), os.path.join(ESM_APT_ROOTDIR, "var/cache/apt/archives/partial"), os.path.join(ESM_APT_ROOTDIR, "var/lib/apt/lists/partial"), ], @@ -548,6 +559,7 @@ except ValueError: # Then we have a bearer token username = "bearer" password = credentials + secret_manager.secrets.add_secret(password) series = system.get_release_info().series if repo_url.endswith("/"): repo_url = repo_url[:-1] @@ -596,6 +608,7 @@ orig_content = system.load_file(apt_auth_file) else: orig_content = "" + repo_auth_line = ( "machine {repo_path} login {login} password {password}" "{cmt}".format( @@ -736,7 +749,7 @@ ] -def get_installed_packages_names(include_versions: bool = False) -> List[str]: +def get_installed_packages_names() -> List[str]: package_list = get_installed_packages() pkg_names = [pkg.name for pkg in package_list] return pkg_names @@ -830,7 +843,7 @@ for file in ESM_BASIC_FILE_STRUCTURE["files"]: system.create_file(file) for folder in ESM_BASIC_FILE_STRUCTURE["folders"]: - os.makedirs(folder, exist_ok=True, mode=755) + os.makedirs(folder, exist_ok=True, mode=0o755) def update_esm_caches(cfg) -> None: @@ -849,7 +862,7 @@ apps_available = False infra_available = False - current_status = cfg.read_cache("status-cache") + current_status = status_cache_file.read() if current_status is None: current_status = status(cfg)[0] @@ -898,7 +911,7 @@ fetch_progress = EsmAcquireProgress() try: cache.update(fetch_progress, sources_list, 0) - except (SystemError) as e: + except SystemError as e: LOG.warning("Failed to fetch the ESM Apt Cache: {}".format(str(e))) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/apt_news.py ubuntu-advantage-tools-32~16.04/uaclient/apt_news.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/apt_news.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/apt_news.py 2024-05-10 17:07:05.000000000 +0000 @@ -8,11 +8,13 @@ import apt_pkg from uaclient import defaults, messages, system, util -from uaclient.api.u.pro.status.is_attached.v1 import _is_attached -from uaclient.apt import ensure_apt_pkg_init +from uaclient.api.u.pro.status.is_attached.v1 import ( + ContractExpiryStatus, + _is_attached, +) +from uaclient.apt import ensure_apt_pkg_init, get_pkg_version, version_compare from uaclient.clouds.identity import get_cloud_type from uaclient.config import UAConfig -from uaclient.contract import ContractExpiryStatus, get_contract_expiry_status from uaclient.data_types import ( BoolDataValue, DataObject, @@ -21,7 +23,7 @@ StringDataValue, data_list, ) -from uaclient.files import state_files +from uaclient.files import notices, state_files LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) @@ -31,6 +33,10 @@ Field("codenames", data_list(StringDataValue), required=False), Field("clouds", data_list(StringDataValue), required=False), Field("pro", BoolDataValue, required=False), + Field("architectures", data_list(StringDataValue), required=False), + Field( + "packages", data_list(data_list(StringDataValue)), required=False + ), ] def __init__( @@ -38,11 +44,15 @@ *, codenames: Optional[List[str]] = None, clouds: Optional[List[str]] = None, - pro: Optional[bool] = None + pro: Optional[bool] = None, + architectures: Optional[List[str]] = None, + packages: Optional[List[List[str]]] = None ): self.codenames = codenames self.clouds = clouds self.pro = pro + self.architectures = architectures + self.packages = packages class AptNewsMessage(DataObject): @@ -67,6 +77,30 @@ self.lines = lines +def _does_package_selector_apply(package_selector): + try: + package_name, version_operator, package_version = package_selector + except ValueError: + LOG.warning("Invalid package selector: %r", package_selector) + return False + installed_package_version = get_pkg_version(package_name) + if installed_package_version is None: + return False + version_comparison = version_compare( + installed_package_version, package_version + ) + return any( + [ + ( + version_comparison == 0 + and version_operator in ["==", "<=", ">="] + ), + (version_comparison < 0 and version_operator in ["<", "<="]), + (version_comparison > 0 and version_operator in [">", ">="]), + ] + ) + + def do_selectors_apply( cfg: UAConfig, selectors: Optional[AptNewsMessageSelectors] ) -> bool: @@ -88,6 +122,19 @@ if selectors.pro != _is_attached(cfg).is_attached: return False + if selectors.architectures is not None: + if system.get_dpkg_arch() not in selectors.architectures: + return False + + if selectors.packages is not None: + if not any( + [ + _does_package_selector_apply(package_selector) + for package_selector in selectors.packages + ] + ): + return False + return True @@ -178,14 +225,22 @@ """ :return: str if local news, None otherwise """ - expiry_status, remaining_days = get_contract_expiry_status(cfg) + is_attached_info = _is_attached(cfg) + expiry_status = is_attached_info.contract_status + remaining_days = is_attached_info.contract_remaining_days + + if expiry_status == ContractExpiryStatus.EXPIRED.value: + notices.add(notices.Notice.CONTRACT_EXPIRED) + return messages.CONTRACT_EXPIRED + + notices.remove(notices.Notice.CONTRACT_EXPIRED) - if expiry_status == ContractExpiryStatus.ACTIVE_EXPIRED_SOON: + if expiry_status == ContractExpiryStatus.ACTIVE_EXPIRED_SOON.value: return messages.CONTRACT_EXPIRES_SOON.pluralize(remaining_days).format( remaining_days=remaining_days ) - if expiry_status == ContractExpiryStatus.EXPIRED_GRACE_PERIOD: + if expiry_status == ContractExpiryStatus.EXPIRED_GRACE_PERIOD.value: grace_period_remaining = ( defaults.CONTRACT_EXPIRY_GRACE_PERIOD_DAYS + remaining_days ) @@ -200,9 +255,6 @@ expired_date=exp_dt_str, remaining_days=grace_period_remaining ) - if expiry_status == ContractExpiryStatus.EXPIRED: - return messages.CONTRACT_EXPIRED - return None diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/__init__.py ubuntu-advantage-tools-32~16.04/uaclient/cli/__init__.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/__init__.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/__init__.py 2024-04-23 13:37:02.000000000 +0000 @@ -3,17 +3,16 @@ import argparse import json import logging -import pathlib import sys import tarfile import tempfile import textwrap import time -from functools import wraps from typing import List, Optional, Tuple # noqa from uaclient import ( actions, + api, apt, apt_news, config, @@ -25,12 +24,15 @@ exceptions, http, lock, + log, + messages, + secret_manager, + security_status, + status, + timer, + util, + version, ) -from uaclient import log as pro_log -from uaclient import messages, security_status -from uaclient import status as ua_status -from uaclient import timer, util, version -from uaclient.api.api import call_api from uaclient.api.u.pro.attach.auto.full_auto_attach.v1 import ( FullAutoAttachOptions, _full_auto_attach, @@ -47,10 +49,9 @@ from uaclient.api.u.pro.security.status.reboot_required.v1 import ( _reboot_required, ) -from uaclient.api.u.pro.status.is_attached.v1 import _is_attached from uaclient.apt import AptProxyScope, setup_apt_proxy +from uaclient.cli import cli_api, cli_util, disable, enable, fix from uaclient.cli.constants import NAME, USAGE_TMPL -from uaclient.cli.fix import set_fix_parser from uaclient.data_types import AttachActionsConfigFile, IncorrectTypeError from uaclient.defaults import PRINT_WRAP_WIDTH from uaclient.entitlements import ( @@ -62,11 +63,10 @@ ApplicationStatus, CanDisableFailure, CanEnableFailure, - CanEnableFailureReason, ) from uaclient.files import notices, state_files from uaclient.files.notices import Notice -from uaclient.log import JsonArrayFormatter +from uaclient.log import get_user_or_root_log_file_path from uaclient.timer.update_messaging import refresh_motd, update_motd_messages from uaclient.yaml import safe_dump, safe_load @@ -74,7 +74,7 @@ STATUS_FORMATS = ["tabular", "json", "yaml"] -UA_COLLECT_LOGS_FILE = "ua_logs.tar.gz" +UA_COLLECT_LOGS_FILE = "pro_logs.tar.gz" event = event_logger.get_event_logger() LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) @@ -174,111 +174,6 @@ return (non_beta_services_desc, beta_services_desc) -def assert_lock_file(lock_holder=None): - """Decorator asserting exclusive access to lock file""" - - def wrapper(f): - @wraps(f) - def new_f(*args, cfg, **kwargs): - with lock.RetryLock( - cfg=cfg, lock_holder=lock_holder, sleep_time=1 - ): - retval = f(*args, cfg=cfg, **kwargs) - return retval - - return new_f - - return wrapper - - -def assert_root(f): - """Decorator asserting root user""" - - @wraps(f) - def new_f(*args, **kwargs): - if not util.we_are_currently_root(): - raise exceptions.NonRootUserError() - else: - return f(*args, **kwargs) - - return new_f - - -def verify_json_format_args(f): - """Decorator to verify if correct params are used for json format""" - - @wraps(f) - def new_f(cmd_args, *args, **kwargs): - if not cmd_args: - return f(cmd_args, *args, **kwargs) - - if cmd_args.format == "json" and not cmd_args.assume_yes: - raise exceptions.CLIJSONFormatRequireAssumeYes() - else: - return f(cmd_args, *args, **kwargs) - - return new_f - - -def assert_attached(raise_custom_error_function=None): - """Decorator asserting attached config. - :param msg_function: Optional function to generate a custom message - if raising an UnattachedError - """ - - def wrapper(f): - @wraps(f) - def new_f(args, cfg, **kwargs): - if not _is_attached(cfg).is_attached: - if raise_custom_error_function: - command = getattr(args, "command", "") - service_names = getattr(args, "service", "") - raise_custom_error_function( - command=command, service_names=service_names, cfg=cfg - ) - else: - raise exceptions.UnattachedError() - return f(args, cfg=cfg, **kwargs) - - return new_f - - return wrapper - - -def assert_not_attached(f): - """Decorator asserting unattached config.""" - - @wraps(f) - def new_f(args, cfg, **kwargs): - if _is_attached(cfg).is_attached: - raise exceptions.AlreadyAttachedError( - account_name=cfg.machine_token_file.account.get("name", "") - ) - return f(args, cfg=cfg, **kwargs) - - return new_f - - -def api_parser(parser): - """Build or extend an arg parser for the api subcommand.""" - parser.prog = "api" - parser.description = messages.CLI_API_DESC - parser.add_argument( - "endpoint_path", metavar="endpoint", help=messages.CLI_API_ENDPOINT - ) - parser.add_argument( - "--args", - dest="options", - default=[], - nargs="*", - help=messages.CLI_API_ARGS, - ) - parser.add_argument( - "--data", dest="data", default="", help=messages.CLI_API_DATA - ) - return parser - - def auto_attach_parser(parser): """Build or extend an arg parser for auto-attach subcommand.""" parser.prog = "auto-attach" @@ -557,92 +452,6 @@ return parser -def enable_parser(parser, cfg: config.UAConfig): - """Build or extend an arg parser for enable subcommand.""" - usage = USAGE_TMPL.format( - name=NAME, command="enable []" - ) - parser.description = messages.CLI_ENABLE_DESC - parser.usage = usage - parser.prog = "enable" - parser._positionals.title = messages.CLI_ARGS - parser._optionals.title = messages.CLI_FLAGS - parser.add_argument( - "service", - action="store", - nargs="+", - help=( - messages.CLI_ENABLE_SERVICE.format( - options=", ".join(entitlements.valid_services(cfg=cfg)) - ) - ), - ) - parser.add_argument( - "--assume-yes", - action="store_true", - help=messages.CLI_ASSUME_YES.format(command="enable"), - ) - parser.add_argument( - "--access-only", - action="store_true", - help=messages.CLI_ENABLE_ACCESS_ONLY, - ) - parser.add_argument( - "--beta", action="store_true", help=messages.CLI_ENABLE_BETA - ) - parser.add_argument( - "--format", - action="store", - choices=["cli", "json"], - default="cli", - help=messages.CLI_FORMAT_DESC.format(default="cli"), - ) - parser.add_argument( - "--variant", action="store", help=messages.CLI_ENABLE_VARIANT - ) - return parser - - -def disable_parser(parser, cfg: config.UAConfig): - """Build or extend an arg parser for disable subcommand.""" - usage = USAGE_TMPL.format( - name=NAME, command="disable []" - ) - parser.description = messages.CLI_DISABLE_DESC - parser.usage = usage - parser.prog = "disable" - parser._positionals.title = messages.CLI_ARGS - parser._optionals.title = messages.CLI_FLAGS - parser.add_argument( - "service", - action="store", - nargs="+", - help=( - messages.CLI_DISABLE_SERVICE.format( - options=", ".join(entitlements.valid_services(cfg=cfg)) - ) - ), - ) - parser.add_argument( - "--assume-yes", - action="store_true", - help=messages.CLI_ASSUME_YES.format(command="disable"), - ) - parser.add_argument( - "--format", - action="store", - choices=["cli", "json"], - default="cli", - help=messages.CLI_FORMAT_DESC.format(default="cli"), - ) - parser.add_argument( - "--purge", - action="store_true", - help=messages.CLI_PURGE, - ) - return parser - - def system_parser(parser): """Build or extend an arg parser for system subcommand.""" parser.usage = USAGE_TMPL.format(name=NAME, command="system ") @@ -720,13 +529,12 @@ ) -def _perform_disable(entitlement, cfg, *, assume_yes, update_status=True): +def _perform_disable(entitlement, cfg, *, json_output, update_status=True): """Perform the disable action on a named entitlement. :param entitlement_name: the name of the entitlement to enable :param cfg: the UAConfig to pass to the entitlement - :param assume_yes: - Assume a yes response for any prompts during service enable + :param json_output: output should be json only @return: True on success, False otherwise """ @@ -736,7 +544,12 @@ if variant is not None: entitlement = variant - ret, reason = entitlement.disable() + if json_output: + progress = api.ProgressWrapper() + else: + progress = api.ProgressWrapper(cli_util.CLIEnableDisableProgress()) + + ret, reason = entitlement.disable(progress) if not ret: event.service_failed(entitlement.name) @@ -753,7 +566,7 @@ event.service_processed(entitlement.name) if update_status: - ua_status.status(cfg=cfg) # Update the status cache + status.status(cfg=cfg) # Update the status cache return ret @@ -800,7 +613,7 @@ print(messages.CLI_CONFIG_GLOBAL_XOR_UA_PROXY) -@assert_root +@cli_util.assert_root def action_config_set(args, *, cfg, **kwargs): """Perform the 'config set' action. @@ -824,6 +637,9 @@ raise exceptions.InvalidArgChoice( arg="", choices=", ".join(config.UA_CONFIGURABLE_KEYS) ) + if not set_value.strip(): + subparser.print_help() + raise exceptions.EmptyConfigValue(arg=set_key) if set_key in ("http_proxy", "https_proxy"): protocol_type = set_key.split("_")[0] if protocol_type == "http": @@ -920,7 +736,7 @@ setattr(cfg, set_key, set_value) -@assert_root +@cli_util.assert_root def action_config_unset(args, *, cfg, **kwargs): """Perform the 'config unset' action. @@ -967,181 +783,18 @@ return 0 -def _raise_enable_disable_unattached_error(command, service_names, cfg): - """Raises a custom error for enable/disable commands when unattached. - - Takes into consideration if the services exist or not, and notify the user - accordingly.""" - (entitlements_found, entitlements_not_found) = get_valid_entitlement_names( - names=service_names, cfg=cfg - ) - if entitlements_found and entitlements_not_found: - raise exceptions.UnattachedMixedServicesError( - valid_service=", ".join(entitlements_found), - operation=command, - invalid_service=", ".join(entitlements_not_found), - service_msg="", - ) - elif entitlements_found: - raise exceptions.UnattachedValidServicesError( - valid_service=", ".join(entitlements_found) - ) - else: - raise exceptions.UnattachedInvalidServicesError( - operation=command, - invalid_service=", ".join(entitlements_not_found), - service_msg="", - ) - - -@verify_json_format_args -@assert_root -@assert_attached(_raise_enable_disable_unattached_error) -@assert_lock_file("pro disable") -def action_disable(args, *, cfg, **kwargs): - """Perform the disable action on a list of entitlements. - - @return: 0 on success, 1 otherwise - """ - if args.purge and args.assume_yes: - raise exceptions.InvalidOptionCombination( - option1="--purge", option2="--assume-yes" - ) - - names = getattr(args, "service", []) - entitlements_found, entitlements_not_found = get_valid_entitlement_names( - names, cfg - ) - ret = True - - for ent_name in entitlements_found: - ent_cls = entitlements.entitlement_factory(cfg=cfg, name=ent_name) - ent = ent_cls(cfg, assume_yes=args.assume_yes, purge=args.purge) - - ret &= _perform_disable(ent, cfg, assume_yes=args.assume_yes) - - if entitlements_not_found: - valid_names = ( - "Try " - + ", ".join(entitlements.valid_services(cfg=cfg, allow_beta=True)) - + "." - ) - service_msg = "\n".join( - textwrap.wrap( - valid_names, - width=80, - break_long_words=False, - break_on_hyphens=False, - ) - ) - raise exceptions.InvalidServiceOpError( - operation="disable", - invalid_service=", ".join(entitlements_not_found), - service_msg=service_msg, - ) - - contract_client = contract.UAContractClient(cfg) - contract_client.update_activity_token() - - event.process_events() - return 0 if ret else 1 - - -@verify_json_format_args -@assert_root -@assert_attached(_raise_enable_disable_unattached_error) -@assert_lock_file("pro enable") -def action_enable(args, *, cfg, **kwargs): - """Perform the enable action on a named entitlement. - - @return: 0 on success, 1 otherwise - """ - variant = getattr(args, "variant", "") - access_only = args.access_only - - if variant and access_only: - raise exceptions.InvalidOptionCombination( - option1="--access-only", option2="--variant" - ) - - event.info(messages.REFRESH_CONTRACT_ENABLE) - try: - contract.refresh(cfg) - except (exceptions.ConnectivityError, exceptions.UbuntuProError): - # Inability to refresh is not a critical issue during enable - LOG.warning("Failed to refresh contract", exc_info=True) - event.warning(warning_msg=messages.E_REFRESH_CONTRACT_FAILURE) - - names = getattr(args, "service", []) - entitlements_found, entitlements_not_found = get_valid_entitlement_names( - names, cfg - ) - ret = True - for ent_name in entitlements_found: - try: - ent_ret, reason = actions.enable_entitlement_by_name( - cfg, - ent_name, - assume_yes=args.assume_yes, - allow_beta=args.beta, - access_only=access_only, - variant=variant, - extra_args=kwargs.get("extra_args"), - ) - ua_status.status(cfg=cfg) # Update the status cache - - if ( - not ent_ret - and reason is not None - and isinstance(reason, CanEnableFailure) - ): - if reason.message is not None: - event.info(reason.message.msg) - event.error( - error_msg=reason.message.msg, - error_code=reason.message.name, - service=ent_name, - ) - if reason.reason == CanEnableFailureReason.IS_BETA: - # if we failed because ent is in beta and there was no - # allow_beta flag/config, pretend it doesn't exist - entitlements_not_found.append(ent_name) - elif ent_ret: - event.service_processed(service=ent_name) - elif not ent_ret and reason is None: - event.service_failed(service=ent_name) - - ret &= ent_ret - except exceptions.UbuntuProError as e: - event.info(e.msg) - event.error( - error_msg=e.msg, error_code=e.msg_code, service=ent_name - ) - ret = False - - if entitlements_not_found: - event.services_failed(entitlements_not_found) - raise create_enable_entitlements_not_found_error( - entitlements_not_found, cfg=cfg, allow_beta=args.beta - ) - - contract_client = contract.UAContractClient(cfg) - contract_client.update_activity_token() - - event.process_events() - return 0 if ret else 1 - - -@verify_json_format_args -@assert_root -@assert_attached() -@assert_lock_file("pro detach") +@cli_util.verify_json_format_args +@cli_util.assert_root +@cli_util.assert_attached() +@cli_util.assert_lock_file("pro detach") def action_detach(args, *, cfg, **kwargs) -> int: """Perform the detach action for this machine. @return: 0 on success, 1 otherwise """ - ret = _detach(cfg, assume_yes=args.assume_yes) + ret = _detach( + cfg, assume_yes=args.assume_yes, json_output=(args.format == "json") + ) if ret == 0: daemon.start() timer.stop() @@ -1149,13 +802,14 @@ return ret -def _detach(cfg: config.UAConfig, assume_yes: bool) -> int: +def _detach(cfg: config.UAConfig, assume_yes: bool, json_output: bool) -> int: """Detach the machine from the active Ubuntu Pro subscription, :param cfg: a ``config.UAConfig`` instance :param assume_yes: Assume a yes answer to any prompts requested. In this case, it means automatically disable any service during detach. + :param json_output: output should be json only @return: 0 on success, 1 otherwise """ @@ -1181,10 +835,12 @@ if not util.prompt_for_confirmation(assume_yes=assume_yes): return 1 for ent in to_disable: - _perform_disable(ent, cfg, assume_yes=assume_yes, update_status=False) + _perform_disable( + ent, cfg, json_output=json_output, update_status=False + ) - cfg.delete_cache() cfg.machine_token_file.delete() + state_files.delete_state_files() update_motd_messages(cfg) event.info(messages.DETACH_SUCCESS) return 0 @@ -1210,22 +866,13 @@ daemon.stop() daemon.cleanup(cfg) - status, _ret = actions.status(cfg) - output = ua_status.format_tabular(status) + status_dict, _ret = actions.status(cfg) + output = status.format_tabular(status_dict) event.info(util.handle_unicode_characters(output)) event.process_events() -def action_api(args, *, cfg, **kwargs): - if args.options and args.data: - raise exceptions.CLIAPIOptionsXORData() - - result = call_api(args.endpoint_path, args.options, args.data, cfg) - print(result.to_json()) - return 0 if result.result == "success" else 1 - - -@assert_root +@cli_util.assert_root def action_auto_attach(args, *, cfg: config.UAConfig, **kwargs) -> int: try: _full_auto_attach( @@ -1274,9 +921,9 @@ return wait_resp.contract_token -@assert_not_attached -@assert_root -@assert_lock_file("pro attach") +@cli_util.assert_not_attached +@cli_util.assert_root +@cli_util.assert_lock_file("pro attach") def action_attach(args, *, cfg, **kwargs): if args.token and args.attach_config: raise exceptions.CLIAttachTokenArgXORConfig() @@ -1285,6 +932,7 @@ enable_services_override = None elif args.token: token = args.token + secret_manager.secrets.add_secret(token) enable_services_override = None else: try: @@ -1388,9 +1036,7 @@ attach_parser(parser_attach) parser_attach.set_defaults(action=action_attach) - parser_api = subparsers.add_parser("api", help=messages.CLI_ROOT_API) - api_parser(parser_api) - parser_api.set_defaults(action=action_api) + cli_api.add_parser(subparsers, cfg) parser_auto_attach = subparsers.add_parser( "auto-attach", help=messages.CLI_ROOT_AUTO_ATTACH @@ -1416,19 +1062,9 @@ detach_parser(parser_detach) parser_detach.set_defaults(action=action_detach) - parser_disable = subparsers.add_parser( - "disable", help=messages.CLI_ROOT_DISABLE - ) - disable_parser(parser_disable, cfg=cfg) - parser_disable.set_defaults(action=action_disable) - - parser_enable = subparsers.add_parser( - "enable", help=messages.CLI_ROOT_ENABLE - ) - enable_parser(parser_enable, cfg=cfg) - parser_enable.set_defaults(action=action_enable) - - set_fix_parser(subparsers) + disable.add_parser(subparsers, cfg) + enable.add_parser(subparsers, cfg) + fix.add_parser(subparsers) parser_security_status = subparsers.add_parser( "security-status", help=messages.CLI_ROOT_SECURITY_STATUS @@ -1471,25 +1107,25 @@ cfg = config.UAConfig() show_all = args.all if args else False token = args.simulate_with_token if args else None - active_value = ua_status.UserFacingConfigStatus.ACTIVE.value - status, ret = actions.status( + active_value = status.UserFacingConfigStatus.ACTIVE.value + status_dict, ret = actions.status( cfg, simulate_with_token=token, show_all=show_all ) - config_active = bool(status["execution_status"] == active_value) + config_active = bool(status_dict["execution_status"] == active_value) if args and args.wait and config_active: - while status["execution_status"] == active_value: + while status_dict["execution_status"] == active_value: event.info(".", end="") time.sleep(1) - status, ret = actions.status( + status_dict, ret = actions.status( cfg, simulate_with_token=token, show_all=show_all, ) event.info("") - event.set_output_content(status) - output = ua_status.format_tabular(status, show_all=show_all) + event.set_output_content(status_dict) + output = status.format_tabular(status_dict, show_all=show_all) event.info(util.handle_unicode_characters(output)) event.process_events() return ret @@ -1525,7 +1161,7 @@ print(messages.REFRESH_CONFIG_SUCCESS) -@assert_attached() +@cli_util.assert_attached() def _action_refresh_contract(_args, cfg: config.UAConfig): try: contract.refresh(cfg) @@ -1550,8 +1186,8 @@ print(messages.REFRESH_MESSAGES_SUCCESS) -@assert_root -@assert_lock_file("pro refresh") +@cli_util.assert_root +@cli_util.assert_lock_file("pro refresh") def action_refresh(args, *, cfg: config.UAConfig, **kwargs): if args.target is None or args.target == "config": _action_refresh_config(args, cfg) @@ -1601,7 +1237,7 @@ if not cfg: cfg = config.UAConfig() - help_response = ua_status.help(cfg, service) + help_response = status.help(cfg, service) if args.format == "json": print(json.dumps(help_response)) @@ -1651,43 +1287,6 @@ ) -def setup_logging(log_level, log_file=None, logger=None): - """Setup console logging and debug logging to log_file - - It configures the pro client logger. - If run as non_root and cfg.log_file is provided, it is replaced - with another non-root log file. - """ - if log_file is None: - cfg = config.UAConfig() - log_file = cfg.log_file - # if we are running as non-root, change log file - if not util.we_are_currently_root(): - log_file = pro_log.get_user_log_file() - - if isinstance(log_level, str): - log_level = log_level.upper() - - if not logger: - logger = logging.getLogger("ubuntupro") - logger.setLevel(log_level) - - # Clear all handlers, so they are replaced for this logger - logger.handlers = [] - - # Setup file logging - log_file_path = pathlib.Path(log_file) - if not log_file_path.exists(): - log_file_path.parent.mkdir(parents=True, exist_ok=True) - log_file_path.touch(mode=0o640) - file_handler = logging.FileHandler(log_file) - file_handler.setFormatter(JsonArrayFormatter()) - file_handler.setLevel(log_level) - file_handler.set_name("upro-file") - file_handler.addFilter(pro_log.RedactionFilter()) - logger.addHandler(file_handler) - - def set_event_mode(cmd_args): """Set the right event mode based on the args provided""" if cmd_args.command in ("attach", "detach", "enable", "disable", "status"): @@ -1768,7 +1367,11 @@ LOG.exception("Unhandled exception, please file a bug") lock.clear_lock_file_if_present() event.info( - info_msg=messages.UNEXPECTED_ERROR.msg, file_type=sys.stderr + info_msg=messages.UNEXPECTED_ERROR.format( + error_msg=str(e), + log_path=get_user_or_root_log_file_path(), + ).msg, + file_type=sys.stderr, ) event.error( error_msg=getattr(e, "msg", str(e)), error_type="exception" @@ -1784,12 +1387,12 @@ @main_error_handler def main(sys_argv=None): - setup_logging( + log.setup_cli_logging( defaults.CONFIG_DEFAULTS["log_level"], defaults.CONFIG_DEFAULTS["log_file"], ) cfg = config.UAConfig() - setup_logging(cfg.log_level, cfg.log_file) + log.setup_cli_logging(cfg.log_level, cfg.log_file) if not sys_argv: sys_argv = sys.argv diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/cli_api.py ubuntu-advantage-tools-32~16.04/uaclient/cli/cli_api.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/cli_api.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/cli_api.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,72 @@ +import json +import sys +from collections import OrderedDict +from typing import Any, Optional # noqa: F401 + +from uaclient import exceptions, messages +from uaclient.api import AbstractProgress +from uaclient.api.api import call_api + + +class CLIAPIProgress(AbstractProgress): + def progress( + self, + *, + total_steps: int, + done_steps: int, + previous_step_message: Optional[str], + current_step_message: Optional[str] + ): + d = OrderedDict() # type: OrderedDict[str, Any] + d["total_steps"] = total_steps + d["done_steps"] = done_steps + d["previous_step_message"] = previous_step_message + d["current_step_message"] = current_step_message + print(json.dumps(d)) + + +def action_api(args, *, cfg, **kwargs): + if args.options and args.data: + raise exceptions.CLIAPIOptionsXORData() + + if args.data and args.data == "-": + if not sys.stdin.isatty(): + args.data = sys.stdin.read() + + if args.show_progress: + progress = CLIAPIProgress() + else: + progress = None + + result = call_api( + args.endpoint_path, args.options, args.data, cfg, progress + ) + print(result.to_json()) + return 0 if result.result == "success" else 1 + + +def add_parser(subparsers, cfg): + """Build or extend an arg parser for the api subcommand.""" + parser = subparsers.add_parser("api", help=messages.CLI_ROOT_API) + parser.prog = "api" + parser.description = messages.CLI_API_DESC + parser.add_argument( + "endpoint_path", metavar="endpoint", help=messages.CLI_API_ENDPOINT + ) + parser.add_argument( + "--show-progress", + action="store_true", + help=messages.CLI_API_SHOW_PROGRESS, + ) + parser.add_argument( + "--args", + dest="options", + default=[], + nargs="*", + help=messages.CLI_API_ARGS, + ) + parser.add_argument( + "--data", dest="data", default="", help=messages.CLI_API_DATA + ) + parser.set_defaults(action=action_api) + return parser diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/cli_util.py ubuntu-advantage-tools-32~16.04/uaclient/cli/cli_util.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/cli_util.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/cli_util.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,150 @@ +from functools import wraps +from typing import Optional + +from uaclient import api, entitlements, exceptions, lock, util +from uaclient.api.u.pro.status.is_attached.v1 import _is_attached + + +class CLIEnableDisableProgress(api.AbstractProgress): + def __init__(self): + self.is_interactive = True + + def progress( + self, + *, + total_steps: int, + done_steps: int, + previous_step_message: Optional[str], + current_step_message: Optional[str] + ): + if current_step_message is not None: + print(current_step_message) + + def _on_event(self, event: str, payload): + if event == "info": + print(payload) + elif event == "message_operation": + if not util.handle_message_operations(payload, print): + raise exceptions.PromptDeniedError() + + +def _null_print(*args, **kwargs): + pass + + +def create_interactive_only_print_function(json_output: bool): + if json_output: + return _null_print + else: + return print + + +def assert_lock_file(lock_holder=None): + """Decorator asserting exclusive access to lock file""" + + def wrapper(f): + @wraps(f) + def new_f(*args, cfg, **kwargs): + with lock.RetryLock(lock_holder=lock_holder, sleep_time=1): + retval = f(*args, cfg=cfg, **kwargs) + return retval + + return new_f + + return wrapper + + +def assert_root(f): + """Decorator asserting root user""" + + @wraps(f) + def new_f(*args, **kwargs): + if not util.we_are_currently_root(): + raise exceptions.NonRootUserError() + else: + return f(*args, **kwargs) + + return new_f + + +def verify_json_format_args(f): + """Decorator to verify if correct params are used for json format""" + + @wraps(f) + def new_f(cmd_args, *args, **kwargs): + if not cmd_args: + return f(cmd_args, *args, **kwargs) + + if cmd_args.format == "json" and not cmd_args.assume_yes: + raise exceptions.CLIJSONFormatRequireAssumeYes() + else: + return f(cmd_args, *args, **kwargs) + + return new_f + + +def assert_attached(raise_custom_error_function=None): + """Decorator asserting attached config. + :param msg_function: Optional function to generate a custom message + if raising an UnattachedError + """ + + def wrapper(f): + @wraps(f) + def new_f(args, cfg, **kwargs): + if not _is_attached(cfg).is_attached: + if raise_custom_error_function: + command = getattr(args, "command", "") + service_names = getattr(args, "service", "") + raise_custom_error_function( + command=command, service_names=service_names, cfg=cfg + ) + else: + raise exceptions.UnattachedError() + return f(args, cfg=cfg, **kwargs) + + return new_f + + return wrapper + + +def assert_not_attached(f): + """Decorator asserting unattached config.""" + + @wraps(f) + def new_f(args, cfg, **kwargs): + if _is_attached(cfg).is_attached: + raise exceptions.AlreadyAttachedError( + account_name=cfg.machine_token_file.account.get("name", "") + ) + return f(args, cfg=cfg, **kwargs) + + return new_f + + +def _raise_enable_disable_unattached_error(command, service_names, cfg): + """Raises a custom error for enable/disable commands when unattached. + + Takes into consideration if the services exist or not, and notify the user + accordingly.""" + ( + entitlements_found, + entitlements_not_found, + ) = entitlements.get_valid_entitlement_names(names=service_names, cfg=cfg) + if entitlements_found and entitlements_not_found: + raise exceptions.UnattachedMixedServicesError( + valid_service=", ".join(entitlements_found), + operation=command, + invalid_service=", ".join(entitlements_not_found), + service_msg="", + ) + elif entitlements_found: + raise exceptions.UnattachedValidServicesError( + valid_service=", ".join(entitlements_found), operation=command + ) + else: + raise exceptions.UnattachedInvalidServicesError( + operation=command, + invalid_service=", ".join(entitlements_not_found), + service_msg="", + ) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/disable.py ubuntu-advantage-tools-32~16.04/uaclient/cli/disable.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/disable.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/disable.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,279 @@ +import json +import logging +import textwrap +from typing import Dict, List # noqa: F401 + +from uaclient import ( + api, + config, + contract, + entitlements, + event_logger, + exceptions, + messages, + status, + util, +) +from uaclient.api.u.pro.services.dependencies.v1 import ( + ServiceWithDependencies, + _dependencies, +) +from uaclient.api.u.pro.status.enabled_services.v1 import _enabled_services +from uaclient.cli import cli_util, constants +from uaclient.entitlements.entitlement_status import CanDisableFailure + +LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) + + +def prompt_for_dependency_handling( + cfg: config.UAConfig, + service: str, + all_dependencies: List[ServiceWithDependencies], + enabled_service_names: List[str], + called_name: str, + service_title: str, +): + dependent_services = [] + for s in all_dependencies: + if s.name == service or s.name not in enabled_service_names: + continue + for requirement in s.depends_on: + if requirement.name == service: + dependent_services.append(s.name) + + for dependent_service in dependent_services: + dependent_service_title = entitlements.get_title( + cfg, dependent_service + ) + user_msg = messages.DEPENDENT_SERVICE.format( + service_being_disabled=service_title, + dependent_service=dependent_service_title, + ) + if not util.prompt_for_confirmation(msg=user_msg): + raise exceptions.DependentServiceStopsDisable( + service_being_disabled=service_title, + dependent_service=dependent_service_title, + ) + + +@cli_util.verify_json_format_args +@cli_util.assert_root +@cli_util.assert_attached(cli_util._raise_enable_disable_unattached_error) +@cli_util.assert_lock_file("pro disable") +def action_disable(args, *, cfg, **kwargs): + """Perform the disable action on a list of entitlements. + + @return: 0 on success, 1 otherwise + """ + processed_services = [] + failed_services = [] + errors = [] + warnings = [] # type: List[Dict[str, str]] + + json_response = { + "_schema_version": event_logger.JSON_SCHEMA_VERSION, + "result": "success", + "needs_reboot": False, + } + + json_output = args.format == "json" + # HACK NOTICE: interactive_only_print here will be a no-op "null_print" + # function defined above if args.format == "json". We use this function + # throughout enable for things that should get printed in the normal + # interactive output so that they don't get printed for the json output. + interactive_only_print = cli_util.create_interactive_only_print_function( + json_output + ) + + if args.purge and args.assume_yes: + raise exceptions.InvalidOptionCombination( + option1="--purge", option2="--assume-yes" + ) + + names = getattr(args, "service", []) + ( + entitlements_found, + entitlements_not_found, + ) = entitlements.get_valid_entitlement_names(names, cfg) + enabled_service_names = [ + s.name for s in _enabled_services(cfg).enabled_services + ] + all_dependencies = _dependencies(cfg).services + + ret = True + for ent_name in entitlements_found: + ent_cls = entitlements.entitlement_factory(cfg=cfg, name=ent_name) + ent = ent_cls(cfg, assume_yes=args.assume_yes, purge=args.purge) + + variant = ent.enabled_variant + if variant is not None: + ent = variant + + if not args.assume_yes: + # this never happens for json output because we assert earlier that + # assume_yes must be True for json output + try: + prompt_for_dependency_handling( + cfg, + ent.name, + all_dependencies, + enabled_service_names, + called_name=ent_name, + service_title=ent.title, + ) + except exceptions.UbuntuProError as e: + LOG.exception(e) + interactive_only_print(e.msg) + interactive_only_print( + messages.ENABLE_FAILED.format(title=ent.title) + ) + ret = False + continue + + if json_output: + progress = api.ProgressWrapper() + else: + progress = api.ProgressWrapper(cli_util.CLIEnableDisableProgress()) + progress.total_steps = ent.calculate_total_disable_steps() + try: + disable_ret, reason = ent.disable(progress) + status.status(cfg=cfg) # Update the status cache + + if not disable_ret: + ret = False + failed_services.append(ent_name) + if reason is not None and isinstance( + reason, CanDisableFailure + ): + if reason.message is not None: + interactive_only_print(reason.message.msg) + errors.append( + { + "type": "service", + "service": ent.name, + "message": reason.message.msg, + "message_code": reason.message.name, + } + ) + else: + processed_services.append(ent_name) + ent_reboot_required = ent._check_for_reboot() + if ent_reboot_required: + json_response["needs_reboot"] = True + interactive_only_print( + messages.ENABLE_REBOOT_REQUIRED_TMPL.format( + operation="disable operation" + ) + ) + except exceptions.UbuntuProError as e: + ret = False + failed_services.append(ent_name) + interactive_only_print(e.msg) + interactive_only_print( + messages.DISABLE_FAILED_TMPL.format(title=ent.title) + ) + errors.append( + { + "type": "service", + "service": ent.name, + "message": e.msg, + "message_code": e.msg_code, + "additional_info": e.additional_info, + } + ) + + if entitlements_not_found: + ret = False + valid_names = ( + "Try " + + ", ".join(entitlements.valid_services(cfg=cfg, allow_beta=True)) + + "." + ) + service_msg = "\n".join( + textwrap.wrap( + valid_names, + width=80, + break_long_words=False, + break_on_hyphens=False, + ) + ) + err = exceptions.InvalidServiceOpError( + operation="disable", + invalid_service=", ".join(entitlements_not_found), + service_msg=service_msg, + ) + interactive_only_print(err.msg) + errors.append( + { + "type": "system", + "service": None, + "message": err.msg, + "message_code": err.msg_code, + "additional_info": err.additional_info, + } + ) + + contract_client = contract.UAContractClient(cfg) + contract_client.update_activity_token() + + if json_output: + processed_services.sort() + failed_services.sort() + + json_response["result"] = "success" if ret else "failure" + json_response["processed_services"] = processed_services + json_response["failed_services"] = failed_services + json_response["errors"] = errors + json_response["warnings"] = warnings + + print( + json.dumps( + json_response, + cls=util.DatetimeAwareJSONEncoder, + sort_keys=True, + ) + ) + + return 0 if ret else 1 + + +def add_parser(subparsers, cfg: config.UAConfig): + """Build or extend an arg parser for disable subcommand.""" + parser = subparsers.add_parser("disable", help=messages.CLI_ROOT_DISABLE) + parser.set_defaults(action=action_disable) + usage = constants.USAGE_TMPL.format( + name=constants.NAME, command="disable []" + ) + parser.description = messages.CLI_DISABLE_DESC + parser.usage = usage + parser.prog = "disable" + parser._positionals.title = messages.CLI_ARGS + parser._optionals.title = messages.CLI_FLAGS + parser.add_argument( + "service", + action="store", + nargs="+", + help=( + messages.CLI_DISABLE_SERVICE.format( + options=", ".join(entitlements.valid_services(cfg=cfg)) + ) + ), + ) + parser.add_argument( + "--assume-yes", + action="store_true", + help=messages.CLI_ASSUME_YES.format(command="disable"), + ) + parser.add_argument( + "--format", + action="store", + choices=["cli", "json"], + default="cli", + help=messages.CLI_FORMAT_DESC.format(default="cli"), + ) + parser.add_argument( + "--purge", + action="store_true", + help=messages.CLI_PURGE, + ) + return parser diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/enable.py ubuntu-advantage-tools-32~16.04/uaclient/cli/enable.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/enable.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/enable.py 2024-05-10 17:07:05.000000000 +0000 @@ -0,0 +1,430 @@ +import json +import logging +from typing import Any, Dict, List + +from uaclient import ( + api, + config, + contract, + entitlements, + event_logger, + exceptions, + messages, + status, + util, +) +from uaclient.api.u.pro.services.dependencies.v1 import ( + ServiceWithDependencies, + _dependencies, +) +from uaclient.api.u.pro.status.enabled_services.v1 import ( + EnabledService, + _enabled_services, +) +from uaclient.api.u.pro.status.is_attached.v1 import _is_attached +from uaclient.cli import cli_util, constants +from uaclient.entitlements.entitlement_status import ( + CanEnableFailure, + CanEnableFailureReason, +) + +LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) + + +def prompt_for_dependency_handling( + cfg: config.UAConfig, + service: str, + all_dependencies: List[ServiceWithDependencies], + enabled_services: List[EnabledService], + called_name: str, + variant: str, + service_title: str, +): + incompatible_services = [] + required_services = [] + enabled_service_names = [s.name for s in enabled_services] + + dependencies = next( + (s for s in all_dependencies if s.name == service), None + ) + if dependencies is not None: + incompatible_services = [ + s.name + for s in dependencies.incompatible_with + if s.name in enabled_service_names + ] + required_services = [ + s.name + for s in dependencies.depends_on + if s.name not in enabled_service_names + ] + + for incompatible_service in incompatible_services: + cfg_block_disable_on_enable = util.is_config_value_true( + config=cfg.cfg, + path_to_value="features.block_disable_on_enable", + ) + incompatible_service_title = entitlements.get_title( + cfg, incompatible_service + ) + user_msg = messages.INCOMPATIBLE_SERVICE.format( + service_being_enabled=service_title, + incompatible_service=incompatible_service_title, + ) + if cfg_block_disable_on_enable or not util.prompt_for_confirmation( + msg=user_msg + ): + raise exceptions.IncompatibleServiceStopsEnable( + service_being_enabled=service_title, + incompatible_service=incompatible_service_title, + ) + + for required_service in required_services: + required_service_title = entitlements.get_title(cfg, required_service) + user_msg = messages.REQUIRED_SERVICE.format( + service_being_enabled=service_title, + required_service=required_service_title, + ) + if not util.prompt_for_confirmation(msg=user_msg): + raise exceptions.RequiredServiceStopsEnable( + service_being_enabled=service_title, + required_service=required_service_title, + ) + + variant_enabled = next( + ( + s + for s in enabled_services + if s.name == service + and s.variant_enabled + and s.variant_name != variant + ), + None, + ) + if variant_enabled is not None and variant is not None: + to_be_enabled_title = entitlements.get_title(cfg, service, variant) + enabled_variant_title = entitlements.get_title( + cfg, service, variant_enabled.variant_name + ) + cfg_block_disable_on_enable = util.is_config_value_true( + config=cfg.cfg, + path_to_value="features.block_disable_on_enable", + ) + user_msg = messages.INCOMPATIBLE_SERVICE.format( + service_being_enabled=to_be_enabled_title, + incompatible_service=enabled_variant_title, + ) + if cfg_block_disable_on_enable or not util.prompt_for_confirmation( + msg=user_msg + ): + raise exceptions.IncompatibleServiceStopsEnable( + service_being_enabled=to_be_enabled_title, + incompatible_service=enabled_variant_title, + ) + + +def print_json_output( + json_output: bool, + json_response: Dict[str, Any], + processed_services: List[str], + failed_services: List[str], + errors: List[Dict[str, Any]], + warnings: List[Dict[str, Any]], + success: bool, +): + if json_output: + processed_services.sort() + failed_services.sort() + + json_response["result"] = "success" if success else "failure" + json_response["processed_services"] = processed_services + json_response["failed_services"] = failed_services + json_response["errors"] = errors + json_response["warnings"] = warnings + + print( + json.dumps( + json_response, + cls=util.DatetimeAwareJSONEncoder, + sort_keys=True, + ) + ) + + +@cli_util.verify_json_format_args +@cli_util.assert_root +@cli_util.assert_attached(cli_util._raise_enable_disable_unattached_error) +@cli_util.assert_lock_file("pro enable") +def action_enable(args, *, cfg, **kwargs) -> int: + """Perform the enable action on a named entitlement. + + @return: 0 on success, 1 otherwise + """ + processed_services = [] # type: List[str] + failed_services = [] # type: List[str] + errors = [] + warnings = [] + + json_response = { + "_schema_version": event_logger.JSON_SCHEMA_VERSION, + "result": "success", + "needs_reboot": False, + } + + json_output = args.format == "json" + # HACK NOTICE: interactive_only_print here will be a no-op "null_print" + # function defined above if args.format == "json". We use this function + # throughout enable for things that should get printed in the normal + # interactive output so that they don't get printed for the json output. + interactive_only_print = cli_util.create_interactive_only_print_function( + json_output + ) + + variant = getattr(args, "variant", "") + access_only = args.access_only + + if variant and access_only: + raise exceptions.InvalidOptionCombination( + option1="--access-only", option2="--variant" + ) + + interactive_only_print(messages.REFRESH_CONTRACT_ENABLE) + try: + contract.refresh(cfg) + except (exceptions.ConnectivityError, exceptions.UbuntuProError): + # Inability to refresh is not a critical issue during enable + LOG.warning("Failed to refresh contract", exc_info=True) + warnings.append( + { + "type": "system", + "message": messages.E_REFRESH_CONTRACT_FAILURE.msg, + "message_code": messages.E_REFRESH_CONTRACT_FAILURE.name, + } + ) + + if not _is_attached(cfg).is_attached_and_contract_valid: + expired_err = exceptions.ContractExpiredError() + interactive_only_print(expired_err.msg) + errors.append( + { + "type": "system", + "message": expired_err.msg, + "message_code": expired_err.msg_code, + } + ) + print_json_output( + json_output, + json_response, + processed_services, + failed_services, + errors, + warnings, + success=False, + ) + return 1 + + names = getattr(args, "service", []) + ( + entitlements_found, + entitlements_not_found, + ) = entitlements.get_valid_entitlement_names(names, cfg) + enabled_services = _enabled_services(cfg).enabled_services + all_dependencies = _dependencies(cfg).services + + ret = True + for ent_name in entitlements.order_entitlements_for_enabling( + cfg, entitlements_found + ): + ent = entitlements.entitlement_factory(cfg, ent_name, variant=variant)( + cfg, + assume_yes=args.assume_yes, + allow_beta=args.beta, + called_name=ent_name, + access_only=access_only, + extra_args=kwargs.get("extra_args"), + ) + LOG.debug("Enabling entitlement %s", ent_name) + LOG.debug("Variant: %s", variant) + real_name = ent.name + ent_title = ent.title + + if not args.assume_yes: + # this never happens for json output because we assert earlier that + # assume_yes must be True for json output + try: + prompt_for_dependency_handling( + cfg, + real_name, + all_dependencies, + enabled_services, + called_name=real_name, + variant=variant, + service_title=ent_title, + ) + except exceptions.UbuntuProError as e: + LOG.exception(e) + interactive_only_print(e.msg) + interactive_only_print( + messages.ENABLE_FAILED.format(title=ent_title) + ) + ret = False + continue + + try: + if json_output: + progress = api.ProgressWrapper() + else: + progress = api.ProgressWrapper( + cli_util.CLIEnableDisableProgress() + ) + + progress.total_steps = ent.calculate_total_enable_steps() + ent_ret, reason = ent.enable(progress) + + status.status(cfg=cfg) # Update the status cache + + if ( + not ent_ret + and reason is not None + and isinstance(reason, CanEnableFailure) + ): + if reason.message is not None: + interactive_only_print(reason.message.msg) + failed_services.append(ent_name) + errors.append( + { + "type": "service", + "service": ent_name, + "message": reason.message.msg, + "message_code": reason.message.name, + } + ) + if reason.reason == CanEnableFailureReason.IS_BETA: + # if we failed because ent is in beta and there was no + # allow_beta flag/config, pretend it doesn't exist + entitlements_not_found.append(ent_name) + interactive_only_print( + messages.ENABLE_FAILED.format(title=ent.title) + ) + elif ent_ret: + processed_services.append(ent_name) + if args.access_only: + interactive_only_print( + messages.ACCESS_ENABLED_TMPL.format(title=ent.title) + ) + else: + interactive_only_print( + messages.ENABLED_TMPL.format(title=ent.title) + ) + ent_reboot_required = ent._check_for_reboot() + if ent_reboot_required: + json_response["needs_reboot"] = True + interactive_only_print( + messages.ENABLE_REBOOT_REQUIRED_TMPL.format( + operation="install" + ) + ) + progress.emit( + "message_operation", ent.messaging.get("post_enable") + ) + elif not ent_ret and reason is None: + failed_services.append(ent_name) + interactive_only_print( + messages.ENABLE_FAILED.format(title=ent.title) + ) + + ret &= ent_ret + except exceptions.UbuntuProError as e: + failed_services.append(ent_name) + interactive_only_print(e.msg) + interactive_only_print( + messages.ENABLE_FAILED.format(title=ent_title) + ) + errors.append( + { + "type": "service", + "service": ent_name, + "message": e.msg, + "message_code": e.msg_code, + "additional_info": e.additional_info, + } + ) + ret = False + + if entitlements_not_found: + ret = False + failed_services += entitlements_not_found + err = entitlements.create_enable_entitlements_not_found_error( + entitlements_not_found, cfg=cfg, allow_beta=args.beta + ) + interactive_only_print(err.msg) + errors.append( + { + "type": "system", + "service": None, + "message": err.msg, + "message_code": err.msg_code, + "additional_info": err.additional_info, + } + ) + + contract_client = contract.UAContractClient(cfg) + contract_client.update_activity_token() + + print_json_output( + json_output, + json_response, + processed_services, + failed_services, + errors, + warnings, + success=ret, + ) + + return 0 if ret else 1 + + +def add_parser(subparsers, cfg: config.UAConfig): + parser = subparsers.add_parser("enable", help=messages.CLI_ROOT_ENABLE) + parser.set_defaults(action=action_enable) + parser.description = messages.CLI_ENABLE_DESC + parser.usage = constants.USAGE_TMPL.format( + name=constants.NAME, command="enable []" + ) + parser.prog = "enable" + parser._positionals.title = messages.CLI_ARGS + parser._optionals.title = messages.CLI_FLAGS + parser.add_argument( + "service", + action="store", + nargs="+", + help=( + messages.CLI_ENABLE_SERVICE.format( + options=", ".join(entitlements.valid_services(cfg=cfg)) + ) + ), + ) + parser.add_argument( + "--assume-yes", + action="store_true", + help=messages.CLI_ASSUME_YES.format(command="enable"), + ) + parser.add_argument( + "--access-only", + action="store_true", + help=messages.CLI_ENABLE_ACCESS_ONLY, + ) + parser.add_argument( + "--beta", action="store_true", help=messages.CLI_ENABLE_BETA + ) + parser.add_argument( + "--format", + action="store", + choices=["cli", "json"], + default="cli", + help=messages.CLI_FORMAT_DESC.format(default="cli"), + ) + parser.add_argument( + "--variant", action="store", help=messages.CLI_ENABLE_VARIANT + ) + return parser diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/fix.py ubuntu-advantage-tools-32~16.04/uaclient/cli/fix.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/fix.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/fix.py 2024-04-23 13:37:02.000000000 +0000 @@ -42,6 +42,7 @@ FixPlanStep, FixPlanUSNResult, FixPlanWarning, + FixPlanWarningFailUpdatingESMCache, FixPlanWarningPackageCannotBeInstalled, FixPlanWarningSecurityIssueNotFixed, NoOpAlreadyFixedData, @@ -52,7 +53,10 @@ from uaclient.api.u.pro.security.fix.cve.plan.v1 import _plan as cve_plan from uaclient.api.u.pro.security.fix.usn.plan.v1 import USNFixPlanOptions from uaclient.api.u.pro.security.fix.usn.plan.v1 import _plan as usn_plan -from uaclient.api.u.pro.status.is_attached.v1 import _is_attached +from uaclient.api.u.pro.status.is_attached.v1 import ( + ContractExpiryStatus, + _is_attached, +) from uaclient.cli.constants import NAME, USAGE_TMPL from uaclient.clouds.identity import ( CLOUD_TYPE_TO_TITLE, @@ -60,7 +64,6 @@ get_cloud_type, ) from uaclient.config import UAConfig -from uaclient.contract import ContractExpiryStatus, get_contract_expiry_status from uaclient.defaults import PRINT_WRAP_WIDTH from uaclient.entitlements import entitlement_factory from uaclient.entitlements.entitlement_status import ( @@ -124,9 +127,9 @@ status=status, pkg_index=self.pkg_index, num_pkgs=len(self.affected_pkgs), - pocket_source=get_pocket_description(pocket) - if pocket - else None, + pocket_source=( + get_pocket_description(pocket) if pocket else None + ), ) ) @@ -137,7 +140,7 @@ ) -def set_fix_parser(subparsers): +def add_parser(subparsers): parser_fix = subparsers.add_parser("fix", help=messages.CLI_ROOT_FIX) parser_fix.set_defaults(action=action_fix) fix_parser(parser_fix) @@ -449,8 +452,11 @@ :returns: True if subscription is expired and not renewed. """ - contract_expiry_status = get_contract_expiry_status(cfg) - if contract_expiry_status[0] == ContractExpiryStatus.EXPIRED: + contract_expiry_status = _is_attached(cfg).contract_status + if ( + contract_expiry_status + and contract_expiry_status == ContractExpiryStatus.EXPIRED.value + ): if dry_run: print(messages.SECURITY_DRY_RUN_UA_EXPIRED_SUBSCRIPTION) return False @@ -649,6 +655,15 @@ fix_context.fix_status = FixStatus.SYSTEM_STILL_VULNERABLE +def _execute_fail_updating_esm_cache_step( + fix_context: FixContext, step: FixPlanWarningFailUpdatingESMCache +): + if util.we_are_currently_root(): + print(messages.CLI_FIX_FAIL_UPDATING_ESM_CACHE) + else: + print("\n" + messages.CLI_FIX_FAIL_UPDATING_ESM_CACHE_NON_ROOT + "\n") + + def _execute_apt_upgrade_step( fix_context: FixContext, step: FixPlanAptUpgradeStep, @@ -846,6 +861,8 @@ _execute_package_cannot_be_installed_step(fix_context, step) if isinstance(step, FixPlanWarningSecurityIssueNotFixed): _execute_security_issue_not_fixed_step(fix_context, step) + if isinstance(step, FixPlanWarningFailUpdatingESMCache): + _execute_fail_updating_esm_cache_step(fix_context, step) if isinstance(step, FixPlanAptUpgradeStep): _execute_apt_upgrade_step(fix_context, step) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli.py 2024-04-23 13:37:02.000000000 +0000 @@ -3,32 +3,20 @@ import json import logging import socket -import sys import textwrap import mock import pytest from uaclient import defaults, exceptions, messages, status -from uaclient.cli import ( - action_help, - assert_attached, - assert_lock_file, - assert_not_attached, - assert_root, - get_parser, - main, - setup_logging, -) +from uaclient.cli import action_help, get_parser, main from uaclient.entitlements import get_valid_entitlement_names from uaclient.exceptions import ( AlreadyAttachedError, LockHeldError, - NonRootUserError, UbuntuProError, UnattachedError, ) -from uaclient.files.notices import Notice BIG_DESC = "123456789 " * 7 + "next line" BIG_URL = "http://" + "adsf" * 10 @@ -105,7 +93,7 @@ "uaclient.config.UAConfig", return_value=FakeConfig(), ): - with mock.patch("uaclient.cli.setup_logging"): + with mock.patch("uaclient.log.setup_cli_logging"): main() out, _err = capsys.readouterr() @@ -356,150 +344,23 @@ M_PATH_UACONFIG = "uaclient.config.UAConfig." -class TestAssertLockFile: - @mock.patch("os.getpid", return_value=123) - @mock.patch(M_PATH_UACONFIG + "delete_cache_key") - @mock.patch("uaclient.files.notices.NoticesManager.add") - @mock.patch(M_PATH_UACONFIG + "write_cache") - def test_assert_root_creates_lock_and_notice( - self, - m_write_cache, - m_add_notice, - m_delete_cache, - _m_getpid, - FakeConfig, - ): - arg, kwarg = mock.sentinel.arg, mock.sentinel.kwarg - - @assert_lock_file("some operation") - def test_function(args, cfg): - assert arg == mock.sentinel.arg - assert kwarg == mock.sentinel.kwarg - - return mock.sentinel.success - - ret = test_function(arg, cfg=FakeConfig()) - assert mock.sentinel.success == ret - lock_msg = "Operation in progress: some operation" - assert [ - mock.call(Notice.OPERATION_IN_PROGRESS, lock_msg) - ] == m_add_notice.call_args_list - assert [mock.call("lock")] == m_delete_cache.call_args_list - assert [ - mock.call("lock", "123:some operation") - ] == m_write_cache.call_args_list - - -class TestAssertRoot: - def test_assert_root_when_root(self): - # autouse mock for we_are_currently_root defaults it to True - arg, kwarg = mock.sentinel.arg, mock.sentinel.kwarg - - @assert_root - def test_function(arg, *, kwarg): - assert arg == mock.sentinel.arg - assert kwarg == mock.sentinel.kwarg - - return mock.sentinel.success - - ret = test_function(arg, kwarg=kwarg) - - assert mock.sentinel.success == ret - - def test_assert_root_when_not_root(self): - @assert_root - def test_function(): - pass - - with mock.patch( - "uaclient.cli.util.we_are_currently_root", return_value=False - ): - with pytest.raises(NonRootUserError): - test_function() - - -# Test multiple uids, to be sure that the root checking is absent -@pytest.mark.parametrize("root", [True, False]) -class TestAssertAttached: - def test_assert_attached_when_attached(self, capsys, root, FakeConfig): - @assert_attached() - def test_function(args, cfg): - return mock.sentinel.success - - cfg = FakeConfig.for_attached_machine() - - with mock.patch( - "uaclient.cli.util.we_are_currently_root", return_value=root - ): - ret = test_function(mock.Mock(), cfg) - - assert mock.sentinel.success == ret - - out, _err = capsys.readouterr() - assert "" == out.strip() - - def test_assert_attached_when_unattached(self, root, FakeConfig): - @assert_attached() - def test_function(args, cfg): - pass - - cfg = FakeConfig() - - with mock.patch( - "uaclient.cli.util.we_are_currently_root", return_value=root - ): - with pytest.raises(UnattachedError): - test_function(mock.Mock(), cfg) - - -@pytest.mark.parametrize("root", [True, False]) -class TestAssertNotAttached: - def test_when_attached(self, root, FakeConfig): - @assert_not_attached - def test_function(args, cfg): - pass - - cfg = FakeConfig.for_attached_machine() - - with mock.patch( - "uaclient.cli.util.we_are_currently_root", return_value=root - ): - with pytest.raises(AlreadyAttachedError): - test_function(mock.Mock(), cfg) - - def test_when_not_attached(self, capsys, root, FakeConfig): - @assert_not_attached - def test_function(args, cfg): - return mock.sentinel.success - - cfg = FakeConfig() - - with mock.patch( - "uaclient.cli.util.we_are_currently_root", return_value=root - ): - ret = test_function(mock.Mock(), cfg) - - assert mock.sentinel.success == ret - - out, _err = capsys.readouterr() - assert "" == out.strip() - - class TestMain: @pytest.mark.parametrize( "exception,expected_error_msg,expected_log", ( ( TypeError("'NoneType' object is not subscriptable"), - messages.UNEXPECTED_ERROR.msg, + messages.UNEXPECTED_ERROR.format( + error_msg="'NoneType' object is not subscriptable", + log_path="/var/log/ubuntu-advantage.log", + ), "Unhandled exception, please file a bug", ), ), ) - @mock.patch(M_PATH_UACONFIG + "delete_cache_key") @mock.patch("uaclient.cli.event.info") @mock.patch("uaclient.cli.LOG.exception") - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch("uaclient.cli.get_parser") def test_errors_handled_gracefully( self, @@ -507,7 +368,6 @@ _m_setup_logging, m_log_exception, m_event_info, - m_delete_cache_key, event, exception, expected_error_msg, @@ -524,13 +384,11 @@ return_value=FakeConfig(), ): main() - assert 0 == m_delete_cache_key.call_count exc = excinfo.value assert 1 == exc.code - assert [ - mock.call(info_msg=expected_error_msg, file_type=mock.ANY) + mock.call(info_msg=expected_error_msg.msg, file_type=mock.ANY) ] == m_event_info.call_args_list assert [mock.call(expected_log)] == m_log_exception.call_args_list @@ -543,16 +401,14 @@ ), ), ) - @mock.patch(M_PATH_UACONFIG + "delete_cache_key") @mock.patch("uaclient.cli.LOG.error") - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch("uaclient.cli.get_parser") def test_interrupt_errors_handled_gracefully( self, m_get_parser, _m_setup_logging, m_log_error, - m_delete_cache_key, exception, expected_log, FakeConfig, @@ -567,7 +423,6 @@ return_value=FakeConfig(), ): main() - assert 0 == m_delete_cache_key.call_count exc = excinfo.value assert 1 == exc.code @@ -591,7 +446,7 @@ ) @mock.patch("uaclient.cli.event.info") @mock.patch("uaclient.cli.LOG.error") - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch("uaclient.cli.get_parser") def test_user_facing_error_handled_gracefully( self, @@ -633,7 +488,7 @@ ) @mock.patch("uaclient.cli.event.info") @mock.patch("uaclient.cli.LOG.exception") - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch("uaclient.cli.get_parser") def test_url_error_handled_gracefully( self, @@ -668,7 +523,7 @@ assert [expected_log_call] == m_log_exception.call_args_list @pytest.mark.parametrize("caplog_text", [logging.DEBUG], indirect=True) - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch("uaclient.cli.get_parser") def test_command_line_is_logged( self, _m_get_parser, _m_setup_logging, caplog_text @@ -680,7 +535,7 @@ assert "['some', 'args']" in log @pytest.mark.parametrize("caplog_text", [logging.DEBUG], indirect=True) - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch("uaclient.cli.get_parser") @mock.patch( "uaclient.cli.util.get_pro_environment", @@ -700,7 +555,7 @@ assert "UA_ENV=YES" in log assert "UA_FEATURES_WOW=XYZ" in log - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch("uaclient.cli.get_parser") @mock.patch("uaclient.cli.config.UAConfig") @pytest.mark.parametrize("config_error", [True, False]) @@ -776,7 +631,7 @@ @mock.patch("uaclient.cli.event.info") @mock.patch("uaclient.cli.action_status") @mock.patch("uaclient.cli.action_security_status") - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch("sys.stdout.isatty") def test_status_human_readable_warning( self, @@ -811,39 +666,6 @@ assert [] == m_event_info.call_args_list -class TestSetupLogging: - def test_correct_handlers_added_to_logger( - self, - FakeConfig, - ): - log_level = logging.DEBUG - logger = logging.getLogger("logger_a") - - handler = logging.StreamHandler(sys.stderr) - handler.setLevel(logging.ERROR) - handler.set_name("ua-test-console") - logger.addHandler(handler) - - with mock.patch( - "uaclient.cli.config.UAConfig", return_value=FakeConfig() - ): - setup_logging(log_level, logger=logger) - assert len(logger.handlers) == 1 - assert logger.handlers[0].name == "upro-file" - assert logger.handlers[0].level == log_level - - @mock.patch("pathlib.Path.touch") - def test_log_file_created_if_not_present(self, m_path_touch, tmpdir): - logger = logging.getLogger("logger_b") - log_file = tmpdir.join("log_file").strpath - setup_logging( - logging.INFO, - log_file=log_file, - logger=logger, - ) - assert m_path_touch.call_args_list == [mock.call(mode=0o640)] - - class TestGetValidEntitlementNames: @mock.patch( "uaclient.cli.entitlements.valid_services", diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_api.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_api.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_api.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_api.py 2024-04-23 13:37:02.000000000 +0000 @@ -5,11 +5,13 @@ import pytest from uaclient import exceptions, messages -from uaclient.cli import action_api, api_parser, get_parser, main +from uaclient.cli import get_parser, main +from uaclient.cli.cli_api import action_api, add_parser HELP_OUTPUT = textwrap.dedent( """\ -usage: api \[-h\] \[--args \[OPTIONS .*\]\] \[--data DATA\] endpoint +usage: api \[-h\] \[--show-progress\] \[--args \[OPTIONS .*\]\](.|\n)*\[--data DATA\](.|\n)* + endpoint Calls the Client API endpoints. @@ -18,6 +20,8 @@ (optional arguments|options): -h, --help show this help message and exit + --show-progress For endpoints that support progress updates, show each(.|\n)* + progress update on a new line in JSON format --args \[OPTIONS .*\](.|\n)*Options to pass to the API endpoint, formatted as(.|\n)* key=value --data DATA arguments in JSON format to the API endpoint @@ -27,7 +31,7 @@ class TestActionAPI: @mock.patch("uaclient.cli.entitlements.valid_services", return_value=[]) - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") def test_api_help(self, _m_setup_logging, valid_services, capsys): with pytest.raises(SystemExit): with mock.patch("sys.argv", ["/usr/bin/ua", "api", "--help"]): @@ -37,20 +41,29 @@ assert re.match(HELP_OUTPUT, out) @pytest.mark.parametrize( - "result,expected_return", (("success", 0), ("failure", 1)) + ["show_progress", "result", "expected_return"], + ((True, "success", 0), (False, "failure", 1)), ) - @mock.patch("uaclient.cli.call_api") - def test_api_action(self, m_call_api, result, expected_return, FakeConfig): + @mock.patch("uaclient.cli.cli_api.call_api") + def test_api_action( + self, m_call_api, show_progress, result, expected_return, FakeConfig + ): m_call_api.return_value.result = result args = mock.MagicMock() args.endpoint_path = "example_endpoint" args.options = [] args.data = "" + args.show_progress = show_progress cfg = FakeConfig() return_code = action_api(args, cfg=cfg) + + if show_progress: + expected_progress = mock.ANY + else: + expected_progress = None assert m_call_api.call_count == 1 assert m_call_api.call_args_list == [ - mock.call("example_endpoint", [], "", cfg) + mock.call("example_endpoint", [], "", cfg, expected_progress) ] assert m_call_api.return_value.to_json.call_count == 1 assert return_code == expected_return @@ -72,9 +85,9 @@ class TestParser: - def test_security_status_parser_updates_parser_config(self, FakeConfig): + def test_api_parser_updates_parser_config(self, FakeConfig): """Update the parser configuration for 'api'.""" - m_parser = api_parser(mock.Mock()) + m_parser = add_parser(mock.MagicMock(), mock.MagicMock()) assert "api" == m_parser.prog full_parser = get_parser(FakeConfig()) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_attach.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_attach.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_attach.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_attach.py 2024-04-23 13:37:02.000000000 +0000 @@ -7,7 +7,7 @@ import mock import pytest -from uaclient import event_logger, http, messages, util +from uaclient import event_logger, http, lock, messages, util from uaclient.cli import ( _post_cli_attach, action_attach, @@ -24,6 +24,7 @@ NonRootUserError, UbuntuProError, ) +from uaclient.files.user_config_file import UserConfigData from uaclient.testing.fakes import FakeFile, FakeUbuntuProError from uaclient.yaml import safe_dump @@ -177,49 +178,44 @@ } assert expected == json.loads(capsys.readouterr()[0]) + @mock.patch("uaclient.lock.check_lock_info") @mock.patch("time.sleep") @mock.patch("uaclient.system.subp") def test_lock_file_exists( self, m_subp, m_sleep, + m_check_lock_info, capsys, FakeConfig, event, ): - """Check when an operation holds a lock file, attach cannot run.""" cfg = FakeConfig() - cfg.write_cache("lock", "123:pro disable") + m_check_lock_info.return_value = (123, "pro disable") + expected_msg = messages.E_LOCK_HELD_ERROR.format( + lock_request="pro attach", lock_holder="pro disable", pid=123 + ) + """Check when an operation holds a lock file, attach cannot run.""" with pytest.raises(LockHeldError) as exc_info: action_attach(mock.MagicMock(), cfg=cfg) - assert [mock.call(["ps", "123"])] * 12 == m_subp.call_args_list - assert ( - "Unable to perform: pro attach.\n" - "Operation in progress: pro disable (pid:123)" - ) == exc_info.value.msg + assert 12 == m_check_lock_info.call_count + assert expected_msg.msg == exc_info.value.msg with pytest.raises(SystemExit): with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): - with mock.patch.object( - cfg, "check_lock_info" - ) as m_check_lock_info: - m_check_lock_info.return_value = (1, "lock_holder") - main_error_handler(action_attach)(mock.MagicMock(), cfg) + main_error_handler(action_attach)(mock.MagicMock(), cfg) - expected_msg = messages.E_LOCK_HELD_ERROR.format( - lock_request="pro attach", lock_holder="lock_holder", pid=1 - ) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "additional_info": { - "lock_holder": "lock_holder", + "lock_holder": "pro disable", "lock_request": "pro attach", - "pid": 1, + "pid": 123, }, "message": expected_msg.msg, "message_code": expected_msg.name, @@ -253,9 +249,15 @@ assert "This machine is now attached to 'test_contract'" in out assert "mock_tabular_status" in out + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) + @mock.patch( + "uaclient.entitlements.check_entitlement_apt_directives_are_unique", + return_value=True, + ) @mock.patch( M_PATH + "contract.UAContractClient.update_activity_token", ) + @mock.patch("uaclient.files.state_files.machine_id_file.write") @mock.patch("uaclient.files.state_files.attachment_data_file.write") @mock.patch("uaclient.system.should_reboot", return_value=False) @mock.patch("uaclient.files.notices.NoticesManager.remove") @@ -270,7 +272,10 @@ _m_remove_notice, _m_should_reboot, _m_attachment_data_file_write, + _m_machine_id_file_write, m_update_activity_token, + _m_check_ent_apt_directives, + _m_check_lock_info, FakeConfig, event, ): @@ -287,7 +292,8 @@ contract_machine_attach.side_effect = fake_contract_attach - ret = action_attach(args, cfg) + with mock.patch.object(lock, "lock_data_file"): + ret = action_attach(args, cfg) assert 0 == ret expected_calls = [ @@ -299,6 +305,7 @@ assert [mock.call(cfg)] == m_post_cli.call_args_list @pytest.mark.parametrize("auto_enable", (True, False)) + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch( M_PATH + "contract.UAContractClient.update_activity_token", ) @@ -319,6 +326,7 @@ _m_should_reboot, _m_attachment_data_file_write, _m_update_activity_token, + _m_check_lock_info, auto_enable, FakeConfig, ): @@ -327,14 +335,17 @@ ) cfg = FakeConfig() - action_attach(args, cfg) + with mock.patch.object(lock, "lock_data_file"): + action_attach(args, cfg) assert [ mock.call(mock.ANY, token="token", allow_enable=auto_enable) ] == m_attach_with_token.call_args_list + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) def test_attach_config_and_token_mutually_exclusive( self, + _m_check_lock_info, FakeConfig, ): args = mock.MagicMock( @@ -342,9 +353,12 @@ ) cfg = FakeConfig() with pytest.raises(UbuntuProError) as e: - action_attach(args, cfg=cfg) + with mock.patch.object(lock, "lock_data_file"): + action_attach(args, cfg=cfg) + assert e.value.msg == messages.E_ATTACH_TOKEN_ARG_XOR_CONFIG.msg + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch( M_PATH + "contract.UAContractClient.update_activity_token", ) @@ -355,6 +369,7 @@ m_attach_with_token, _m_post_cli_attach, m_update_activity_token, + _m_check_lock_info, FakeConfig, ): args = mock.MagicMock( @@ -362,14 +377,18 @@ attach_config=FakeFile(safe_dump({"token": "faketoken"})), ) cfg = FakeConfig() - action_attach(args, cfg=cfg) + with mock.patch.object(lock, "lock_data_file"): + action_attach(args, cfg=cfg) + assert [ mock.call(mock.ANY, token="faketoken", allow_enable=True) ] == m_attach_with_token.call_args_list assert 1 == m_update_activity_token.call_count + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) def test_attach_config_invalid_config( self, + _m_check_lock_info, FakeConfig, capsys, event, @@ -383,7 +402,8 @@ ) cfg = FakeConfig() with pytest.raises(UbuntuProError) as e: - action_attach(args, cfg=cfg) + with mock.patch.object(lock, "lock_data_file"): + action_attach(args, cfg=cfg) assert "Error while reading fakename:" in e.value.msg args.attach_config = FakeFile( @@ -394,7 +414,8 @@ with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): - main_error_handler(action_attach)(args, cfg) + with mock.patch.object(lock, "lock_data_file"): + main_error_handler(action_attach)(args, cfg) expected_message = messages.E_ATTACH_CONFIG_READ_ERROR.format( config_name="fakename", @@ -432,6 +453,7 @@ assert expected == json.loads(capsys.readouterr()[0]) @pytest.mark.parametrize("auto_enable", (True, False)) + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch( M_PATH + "contract.UAContractClient.update_activity_token", ) @@ -455,6 +477,7 @@ m_attach_with_token, m_enable, m_update_activity_token, + _m_check_lock_info, auto_enable, FakeConfig, event, @@ -471,7 +494,9 @@ ), auto_enable=auto_enable, ) - action_attach(args, cfg=cfg) + with mock.patch.object(lock, "lock_data_file"): + action_attach(args, cfg=cfg) + assert [ mock.call(mock.ANY, token="faketoken", allow_enable=False) ] == m_attach_with_token.call_args_list @@ -492,7 +517,8 @@ with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): - main_error_handler(action_attach)(args, cfg) + with mock.patch.object(lock, "lock_data_file"): + main_error_handler(action_attach)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, @@ -515,25 +541,51 @@ ), ( Exception("error"), - messages.UNEXPECTED_ERROR, + messages.UNEXPECTED_ERROR.format( + error_msg="error", + log_path="/var/log/ubuntu-advantage.log", + ), messages.E_ATTACH_FAILURE_UNEXPECTED, ), ), ) + @mock.patch("uaclient.files.state_files.status_cache_file.write") + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) + @mock.patch( + "uaclient.entitlements.check_entitlement_apt_directives_are_unique", + return_value=True, + ) + @mock.patch( + "uaclient.files.state_files.machine_id_file.read", return_value=None + ) + @mock.patch("uaclient.files.state_files.machine_id_file.write") @mock.patch("uaclient.files.state_files.attachment_data_file.write") @mock.patch("uaclient.entitlements.entitlements_enable_order") - @mock.patch("uaclient.contract.process_entitlement_delta") + @mock.patch("uaclient.actions.enable_entitlement_by_name") + @mock.patch("uaclient.contract.get_enabled_by_default_services") @mock.patch("uaclient.contract.apply_contract_overrides") @mock.patch("uaclient.contract.UAContractClient.request_url") @mock.patch("uaclient.timer.update_messaging.update_motd_messages") + @mock.patch( + "uaclient.files.user_config_file.UserConfigFileObject.public_config", + new_callable=mock.PropertyMock, + return_value=UserConfigData(), + ) def test_attach_when_one_service_fails_to_enable( self, + m_public_config, _m_update_messages, m_request_url, _m_apply_contract_overrides, - m_process_entitlement_delta, + m_get_enabled_by_default_services, + m_enable_ent_by_name, m_enable_order, _m_attachment_data_file_write, + _m_machine_id_file_write, + _m_machine_id_file_read, + _m_check_ent_apt_directives, + _m_check_lock_info, + _m_status_cache_file, expected_exception, expected_msg, expected_outer_msg, @@ -544,10 +596,17 @@ cfg = FakeConfig() m_enable_order.return_value = ["test1", "test2"] - m_process_entitlement_delta.side_effect = [ - ({"test": 123}, True), + m_enable_ent_by_name.side_effect = [ + (True, None), expected_exception, ] + m_ent1 = mock.MagicMock(variant="") + type(m_ent1).name = mock.PropertyMock(return_value="test1") + + m_ent2 = mock.MagicMock(variant="") + type(m_ent2).name = mock.PropertyMock(return_value="test2") + + m_get_enabled_by_default_services.return_value = [m_ent1, m_ent2] m_request_url.return_value = http.HTTPResponse( code=200, headers={}, @@ -591,7 +650,8 @@ "_event_logger_mode", event_logger.EventLoggerMode.JSON, ): - main_error_handler(action_attach)(args, cfg) + with mock.patch.object(lock, "lock_data_file"): + main_error_handler(action_attach)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, @@ -620,6 +680,7 @@ } assert expected == json.loads(fake_stdout.getvalue()) + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch(M_PATH + "_initiate") @mock.patch(M_PATH + "_wait") @mock.patch(M_PATH + "_revoke") @@ -628,6 +689,7 @@ m_revoke, m_wait, m_initiate, + _m_check_lock_info, FakeConfig, ): m_initiate.return_value = mock.MagicMock( @@ -637,17 +699,22 @@ m_args = mock.MagicMock(token=None, attach_config=None) with pytest.raises(MagicAttachTokenError): - action_attach(args=m_args, cfg=FakeConfig()) + with mock.patch.object(lock, "lock_data_file"): + action_attach(args=m_args, cfg=FakeConfig()) assert 1 == m_initiate.call_count assert 1 == m_wait.call_count assert 1 == m_revoke.call_count - def test_magic_attach_fails_if_format_json_param_used(self, FakeConfig): + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) + def test_magic_attach_fails_if_format_json_param_used( + self, _m_check_lock_info, FakeConfig + ): m_args = mock.MagicMock(token=None, attach_config=None, format="json") with pytest.raises(MagicAttachInvalidParam) as exc_info: - action_attach(args=m_args, cfg=FakeConfig()) + with mock.patch.object(lock, "lock_data_file"): + action_attach(args=m_args, cfg=FakeConfig()) assert ( "This attach flow does not support --format with value: json" @@ -656,7 +723,7 @@ @mock.patch(M_PATH + "contract.get_available_resources") class TestParser: - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") def test_attach_help( self, _m_resources, _m_setup_logging, capsys, FakeConfig ): diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_auto_attach.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_auto_attach.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_auto_attach.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_auto_attach.py 2024-04-23 13:37:02.000000000 +0000 @@ -43,7 +43,7 @@ class TestActionAutoAttach: - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch(M_PATH + "contract.get_available_resources") def test_auto_attach_help( self, _m_resources, _m_setup_logging, capsys, FakeConfig diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_collect_logs.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_collect_logs.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_collect_logs.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_collect_logs.py 2024-04-23 13:37:02.000000000 +0000 @@ -24,13 +24,13 @@ -h, --help show this help message and exit -o OUTPUT, --output OUTPUT tarball where the logs will be stored. \(Defaults to - ./ua_logs.tar.gz\) + ./pro_logs.tar.gz\) """ # noqa ) class TestActionCollectLogs: - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch(M_PATH + "contract.get_available_resources") def test_collect_logs_help( self, _m_resources, _m_setup_logging, capsys, FakeConfig @@ -67,6 +67,7 @@ @mock.patch("os.chown") @mock.patch("os.path.isfile", return_value=True) @mock.patch("shutil.copy") + @mock.patch("uaclient.actions.status") @mock.patch("uaclient.system.write_file") @mock.patch("uaclient.system.load_file") @mock.patch("uaclient.system.subp", return_value=(None, None)) @@ -81,6 +82,7 @@ m_subp, _load_file, _write_file, + m_status, m_shutilcopy, m_isfile, _chown, @@ -96,6 +98,7 @@ FakeConfig, tmpdir, ): + m_status.return_value = {"user-id": ""}, 0 m_get_release_info.return_value.series = series util_we_are_currently_root.return_value = is_root m_get_user.return_value = tmpdir.join("user-log").strpath @@ -116,7 +119,6 @@ assert m_subp.call_args_list == [ mock.call(["cloud-id"], rcs=None), - mock.call(["pro", "status", "--format", "json"], rcs=None), mock.call(["/snap/bin/canonical-livepatch", "status"], rcs=None), mock.call(["systemctl", "list-timers", "--all"], rcs=None), mock.call( diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_config.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_config.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_config.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_config.py 2024-04-23 13:37:02.000000000 +0000 @@ -22,7 +22,7 @@ @mock.patch("uaclient.cli.LOG.error") -@mock.patch("uaclient.cli.setup_logging") +@mock.patch("uaclient.log.setup_cli_logging") @mock.patch(M_PATH + "contract.get_available_resources") class TestMainConfig: @pytest.mark.parametrize("additional_params", ([], ["--help"])) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_config_set.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_config_set.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_config_set.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_config_set.py 2024-04-23 13:37:02.000000000 +0000 @@ -25,7 +25,7 @@ M_LIVEPATCH = "uaclient.entitlements.livepatch." -@mock.patch("uaclient.cli.setup_logging") +@mock.patch("uaclient.log.setup_cli_logging") class TestMainConfigSet: @pytest.mark.parametrize( "kv_pair,err_msg", @@ -55,6 +55,14 @@ " global_apt_https_proxy, update_messaging_timer," " metering_timer", ), + ( + "http_proxy=", + "Empty value provided for http_proxy.", + ), + ( + "https_proxy= ", + "Empty value provided for https_proxy.", + ), ), ) @mock.patch("uaclient.cli.contract.get_available_resources") @@ -83,12 +91,16 @@ assert err_msg in err -@mock.patch("uaclient.config.state_files.user_config_file.write") +@mock.patch("uaclient.config.user_config_file.user_config.write") @mock.patch("uaclient.cli.contract.get_available_resources") class TestActionConfigSet: @mock.patch("uaclient.util.we_are_currently_root", return_value=False) def test_set_error_on_non_root_user( - self, _m_resources, _we_are_currently_root, _write, FakeConfig + self, + _m_resources, + _we_are_currently_root, + _write_, + FakeConfig, ): """Root is required to run pro config set.""" args = mock.MagicMock(key_value_pair="something=1") @@ -270,13 +282,6 @@ "https://proxy", "https://proxy", ), - ( - "global_apt_http_proxy", - "", - apt.AptProxyScope.GLOBAL, - "https://proxy", - "https://proxy", - ), ), ) @mock.patch("uaclient.cli.configure_apt_proxy") @@ -379,13 +384,6 @@ apt.AptProxyScope.UACLIENT, "https://proxy", "https://proxy", - ), - ( - "ua_apt_https_proxy", - "", - apt.AptProxyScope.UACLIENT, - "https://proxy", - "https://proxy", ), ), ) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_config_show.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_config_show.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_config_show.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_config_show.py 2024-04-23 13:37:02.000000000 +0000 @@ -17,7 +17,7 @@ @mock.patch("uaclient.cli.logging.error") -@mock.patch("uaclient.cli.setup_logging") +@mock.patch("uaclient.log.setup_cli_logging") @mock.patch(M_PATH + "contract.get_available_resources") class TestMainConfigShow: def test_config_show_help( diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_config_unset.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_config_unset.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_config_unset.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_config_unset.py 2024-04-23 13:37:02.000000000 +0000 @@ -24,7 +24,7 @@ M_LIVEPATCH = "uaclient.entitlements.livepatch." -@mock.patch("uaclient.cli.setup_logging") +@mock.patch("uaclient.log.setup_cli_logging") @mock.patch("uaclient.cli.contract.get_available_resources") class TestMainConfigUnSet: @pytest.mark.parametrize( @@ -73,7 +73,7 @@ assert err_msg in err -@mock.patch("uaclient.config.state_files.user_config_file.write") +@mock.patch("uaclient.config.user_config_file.user_config.write") class TestActionConfigUnSet: @mock.patch("uaclient.util.we_are_currently_root", return_value=False) def test_set_error_on_non_root_user( diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_detach.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_detach.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_detach.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_detach.py 2024-04-23 13:37:02.000000000 +0000 @@ -6,7 +6,7 @@ import mock import pytest -from uaclient import event_logger, exceptions, messages +from uaclient import event_logger, exceptions, lock, messages from uaclient.cli import ( action_detach, detach_parser, @@ -103,12 +103,14 @@ } assert expected == json.loads(capsys.readouterr()[0]) + @mock.patch("uaclient.lock.check_lock_info") @mock.patch("time.sleep") @mock.patch("uaclient.system.subp") def test_lock_file_exists( self, m_subp, m_sleep, + m_check_lock_info, m_prompt, FakeConfig, capsys, @@ -117,10 +119,12 @@ """Check when an operation holds a lock file, detach cannot run.""" cfg = FakeConfig.for_attached_machine() args = mock.MagicMock() - cfg.write_cache("lock", "123:pro enable") + m_check_lock_info.return_value = (123, "pro enable") + with pytest.raises(exceptions.LockHeldError) as err: action_detach(args, cfg=cfg) - assert [mock.call(["ps", "123"])] * 12 == m_subp.call_args_list + + assert 12 == m_check_lock_info.call_count expected_error_msg = messages.E_LOCK_HELD_ERROR.format( lock_request="pro detach", lock_holder="pro enable", pid="123" ) @@ -159,6 +163,8 @@ "prompt_response,assume_yes,expect_disable", [(True, False, True), (False, False, False), (True, True, True)], ) + @mock.patch("uaclient.files.state_files.delete_state_files") + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.contract.UAContractClient") @mock.patch("uaclient.cli.update_motd_messages") @mock.patch("uaclient.cli.entitlements_disable_order") @@ -169,6 +175,8 @@ m_disable_order, m_update_apt_and_motd_msgs, m_client, + _m_check_lock_info, + _m_delete_state_files, m_prompt, prompt_response, assume_yes, @@ -195,7 +203,8 @@ m_ent_factory.return_value = disabled_cls args = mock.MagicMock(assume_yes=assume_yes) - return_code = action_detach(args, cfg=cfg) + with mock.patch.object(lock, "lock_data_file"): + return_code = action_detach(args, cfg=cfg) assert [ mock.call(ignore_dependent_services=True) @@ -203,7 +212,7 @@ if expect_disable: assert [ - mock.call() + mock.call(mock.ANY) ] == disabled_cls.return_value.disable.call_args_list assert 0 == return_code else: @@ -223,7 +232,8 @@ with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): - main_error_handler(action_detach)(args, cfg) + with mock.patch.object(lock, "lock_data_file"): + main_error_handler(action_detach)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, @@ -236,82 +246,66 @@ } assert expected == json.loads(fake_stdout.getvalue()) + @mock.patch("uaclient.files.state_files.delete_state_files") + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) + @mock.patch("uaclient.cli.cli_util._is_attached") @mock.patch("uaclient.cli.entitlements_disable_order") - @mock.patch("uaclient.contract.UAContractClient") - @mock.patch("uaclient.cli.update_motd_messages") - def test_config_cache_deleted( - self, - m_update_apt_and_motd_msgs, - m_client, - m_disable_order, - _m_prompt, - FakeConfig, - tmpdir, - ): - m_disable_order.return_value = [] - - fake_client = FakeContractClient(FakeConfig.for_attached_machine()) - m_client.return_value = fake_client - - m_cfg = mock.MagicMock() - m_cfg.check_lock_info.return_value = (-1, "") - m_cfg.data_path.return_value = tmpdir.join("lock").strpath - action_detach(mock.MagicMock(), m_cfg) - - assert [mock.call()] == m_cfg.delete_cache.call_args_list - assert [mock.call(m_cfg)] == m_update_apt_and_motd_msgs.call_args_list - - @mock.patch("uaclient.cli.entitlements_disable_order") - @mock.patch("uaclient.contract.UAContractClient") @mock.patch("uaclient.cli.update_motd_messages") def test_correct_message_emitted( self, m_update_apt_and_motd_msgs, - m_client, m_disable_order, + m_is_attached, + _m_check_lock_info, + m_delete_state_files, _m_prompt, capsys, - FakeConfig, tmpdir, ): m_disable_order.return_value = [] - - fake_client = FakeContractClient(FakeConfig.for_attached_machine()) - m_client.return_value = fake_client + m_is_attached.return_value = mock.MagicMock( + is_attached=True, + contract_status="active", + contract_remaining_days=100, + ) m_cfg = mock.MagicMock() - m_cfg.check_lock_info.return_value = (-1, "") - m_cfg.data_path.return_value = tmpdir.join("lock").strpath - action_detach(mock.MagicMock(), m_cfg) + with mock.patch.object(lock, "lock_data_file"): + action_detach(mock.MagicMock(), m_cfg) out, _err = capsys.readouterr() - assert messages.DETACH_SUCCESS + "\n" == out assert [mock.call(m_cfg)] == m_update_apt_and_motd_msgs.call_args_list + assert [mock.call()] == m_delete_state_files.call_args_list + @mock.patch("uaclient.files.state_files.delete_state_files") + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) + @mock.patch("uaclient.cli.cli_util._is_attached") @mock.patch("uaclient.cli.entitlements_disable_order") - @mock.patch("uaclient.contract.UAContractClient") @mock.patch("uaclient.cli.update_motd_messages") def test_returns_zero( self, m_update_apt_and_motd_msgs, - m_client, m_disable_order, + m_is_attached, + _m_check_lock_info, + m_delete_state_files, _m_prompt, - FakeConfig, tmpdir, ): m_disable_order.return_value = [] - - fake_client = FakeContractClient(FakeConfig.for_attached_machine()) - m_client.return_value = fake_client + m_is_attached.return_value = mock.MagicMock( + is_attached=True, + contract_status="active", + contract_remaining_days=100, + ) m_cfg = mock.MagicMock() - m_cfg.check_lock_info.return_value = (-1, "") - m_cfg.data_path.return_value = tmpdir.join("lock").strpath - ret = action_detach(mock.MagicMock(), m_cfg) + with mock.patch.object(lock, "lock_data_file"): + ret = action_detach(mock.MagicMock(), m_cfg) assert 0 == ret + assert [mock.call()] == m_delete_state_files.call_args_list assert [mock.call(m_cfg)] == m_update_apt_and_motd_msgs.call_args_list @pytest.mark.parametrize( @@ -347,7 +341,9 @@ ), ], ) - @mock.patch("uaclient.contract.UAContractClient") + @mock.patch("uaclient.files.state_files.delete_state_files") + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) + @mock.patch("uaclient.cli.cli_util._is_attached") @mock.patch("uaclient.cli.update_motd_messages") @mock.patch("uaclient.entitlements.entitlement_factory") @mock.patch("uaclient.cli.entitlements_disable_order") @@ -356,29 +352,32 @@ m_disable_order, m_ent_factory, m_update_apt_and_motd_msgs, - m_client, + m_is_attached, + _m_check_lock_info, + _m_delete_state_files, _m_prompt, capsys, classes, disable_order, expected_message, disabled_services, - FakeConfig, tmpdir, + FakeConfig, event, ): m_ent_factory.side_effect = classes m_disable_order.return_value = disable_order - - fake_client = FakeContractClient(FakeConfig.for_attached_machine()) - m_client.return_value = fake_client + m_is_attached.return_value = mock.MagicMock( + is_attached=True, + contract_status="active", + contract_remaining_days=100, + ) m_cfg = mock.MagicMock() - m_cfg.check_lock_info.return_value = (-1, "") - m_cfg.data_path.return_value = tmpdir.join("lock").strpath args = mock.MagicMock() - action_detach(args, m_cfg) + with mock.patch.object(lock, "lock_data_file"): + action_detach(args, m_cfg) out, _err = capsys.readouterr() @@ -392,7 +391,8 @@ with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): - main_error_handler(action_detach)(args, cfg) + with mock.patch.object(lock, "lock_data_file"): + main_error_handler(action_detach)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_disable.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_disable.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_disable.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_disable.py 2024-04-23 13:37:02.000000000 +0000 @@ -6,12 +6,18 @@ import mock import pytest -from uaclient import entitlements, event_logger, exceptions, messages -from uaclient.cli import action_disable, main, main_error_handler +from uaclient import entitlements, event_logger, exceptions, lock, messages +from uaclient.api.u.pro.services.dependencies.v1 import ( + ServiceWithDependencies, + ServiceWithReason, +) +from uaclient.cli import main, main_error_handler +from uaclient.cli.disable import action_disable, prompt_for_dependency_handling from uaclient.entitlements.entitlement_status import ( CanDisableFailure, CanDisableFailureReason, ) +from uaclient.testing.helpers import does_not_raise @pytest.fixture @@ -55,7 +61,7 @@ class TestDisable: - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch("uaclient.cli.contract.get_available_resources") def test_disable_help( self, _m_resources, _m_setup_logging, capsys, FakeConfig @@ -75,6 +81,7 @@ @pytest.mark.parametrize( "disable_return,return_code", ((True, 0), (False, 1)) ) + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch( "uaclient.cli.contract.UAContractClient.update_activity_token", ) @@ -87,6 +94,7 @@ m_valid_services, m_entitlement_factory, m_update_activity_token, + _m_check_lock_info, disable_return, return_code, assume_yes, @@ -123,6 +131,7 @@ type(m_entitlement).name = mock.PropertyMock( return_value=entitlement_name ) + m_entitlement._check_for_reboot.return_value = False def factory_side_effect(cfg, name, ent_dict=ent_dict): return ent_dict.get(name) @@ -135,7 +144,7 @@ args_mock.assume_yes = assume_yes args_mock.purge = False - with mock.patch.object(cfg, "check_lock_info", return_value=(-1, "")): + with mock.patch.object(lock, "lock_data_file"): ret = action_disable(args_mock, cfg=cfg) for m_entitlement_cls in entitlements_cls: @@ -143,7 +152,7 @@ mock.call(cfg, assume_yes=assume_yes, purge=False) ] == m_entitlement_cls.call_args_list - expected_disable_call = mock.call() + expected_disable_call = mock.call(mock.ANY) for m_entitlement in entitlements_obj: assert [ expected_disable_call @@ -160,9 +169,7 @@ event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): with mock.patch.object(event, "set_event_mode"): - with mock.patch.object( - cfg, "check_lock_info", return_value=(-1, "") - ): + with mock.patch.object(lock, "lock_data_file"): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): ret = action_disable(args_mock, cfg=cfg) @@ -192,6 +199,8 @@ assert expected == json.loads(fake_stdout.getvalue()) @pytest.mark.parametrize("assume_yes", (True, False)) + @mock.patch("uaclient.contract.UAContractClient.update_activity_token") + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.entitlements.entitlement_factory") @mock.patch("uaclient.entitlements.valid_services") @mock.patch("uaclient.status.status") @@ -200,6 +209,8 @@ m_status, m_valid_services, m_entitlement_factory, + _m_check_lock_info, + _m_update_activity_token, assume_yes, tmpdir, event, @@ -219,6 +230,7 @@ ), ) type(m_ent1_obj).name = mock.PropertyMock(return_value="ent1") + m_ent1_obj._check_for_reboot.return_value = False m_ent2_cls = mock.Mock() m_ent2_obj = m_ent2_cls.return_value @@ -231,12 +243,14 @@ ), ) type(m_ent2_obj).name = mock.PropertyMock(return_value="ent2") + m_ent2_obj._check_for_reboot.return_value = False m_ent3_cls = mock.Mock() m_ent3_obj = m_ent3_cls.return_value m_ent3_obj.enabled_variant = None m_ent3_obj.disable.return_value = (True, None) type(m_ent3_obj).name = mock.PropertyMock(return_value="ent3") + m_ent3_obj._check_for_reboot.return_value = False def factory_side_effect(cfg, name): if name == "ent2": @@ -254,10 +268,9 @@ args_mock.assume_yes = assume_yes args_mock.purge = False - with pytest.raises(exceptions.UbuntuProError) as err: - with mock.patch.object( - cfg, "check_lock_info", return_value=(-1, "") - ): + first_fake_stdout = io.StringIO() + with contextlib.redirect_stdout(first_fake_stdout): + with mock.patch.object(lock, "lock_data_file"): action_disable(args_mock, cfg=cfg) assert ( @@ -266,7 +279,7 @@ invalid_service="ent1", service_msg="Try ent2, ent3.", ).msg - == err.value.msg + in first_fake_stdout.getvalue() ) for m_ent_cls in [m_ent2_cls, m_ent3_cls]: @@ -274,7 +287,7 @@ mock.call(cfg, assume_yes=assume_yes, purge=False) ] == m_ent_cls.call_args_list - expected_disable_call = mock.call() + expected_disable_call = mock.call(mock.ANY) for m_ent in [m_ent2_obj, m_ent3_obj]: assert [expected_disable_call] == m_ent.disable.call_args_list @@ -284,19 +297,10 @@ cfg = FakeConfig.for_attached_machine() args_mock.assume_yes = True args_mock.format = "json" - with pytest.raises(SystemExit): - with mock.patch.object( - event, "_event_logger_mode", event_logger.EventLoggerMode.JSON - ): - with mock.patch.object(event, "set_event_mode"): - with mock.patch.object( - cfg, "check_lock_info", return_value=(-1, "") - ): - fake_stdout = io.StringIO() - with contextlib.redirect_stdout(fake_stdout): - main_error_handler(action_disable)( - args_mock, cfg=cfg - ) + with mock.patch.object(lock, "lock_data_file"): + fake_stdout = io.StringIO() + with contextlib.redirect_stdout(fake_stdout): + main_error_handler(action_disable)(args_mock, cfg=cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, @@ -334,14 +338,17 @@ @pytest.mark.parametrize( "root,expected_error_template", [ - (True, messages.E_INVALID_SERVICE_OP_FAILURE), (False, messages.E_NONROOT_USER), ], ) + @mock.patch("uaclient.contract.UAContractClient.update_activity_token") + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.util.we_are_currently_root") def test_invalid_service_error_message( self, m_we_are_currently_root, + _m_check_lock_info, + _m_update_activity_token, root, expected_error_template, FakeConfig, @@ -354,25 +361,15 @@ cfg = FakeConfig.for_attached_machine() args = mock.MagicMock() args.purge = False + args.service = ["esm-infra"] - if root: - expected_error = expected_error_template.format( - operation="disable", - invalid_service="bogus", - service_msg=all_service_msg, - ) - expected_info = { - "operation": "disable", - "invalid_service": "bogus", - "service_msg": all_service_msg, - } - else: - expected_error = expected_error_template - expected_info = None + expected_error = expected_error_template + expected_info = None with pytest.raises(exceptions.UbuntuProError) as err: - args.service = ["bogus"] - action_disable(args, cfg) + with mock.patch.object(lock, "lock_data_file"): + action_disable(args, cfg) + assert expected_error.msg == err.value.msg args.assume_yes = True @@ -384,7 +381,8 @@ with mock.patch.object(event, "set_event_mode"): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): - main_error_handler(action_disable)(args, cfg) + with mock.patch.object(lock, "lock_data_file"): + main_error_handler(action_disable)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, @@ -407,8 +405,12 @@ assert expected == json.loads(fake_stdout.getvalue()) @pytest.mark.parametrize("service", [["bogus"], ["bogus1", "bogus2"]]) + @mock.patch("uaclient.contract.UAContractClient.update_activity_token") + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) def test_invalid_service_names( self, + _m_check_lock_info, + _m_update_activity_token, service, FakeConfig, event, @@ -424,22 +426,20 @@ invalid_service=", ".join(sorted(service)), service_msg=all_service_msg, ) - with pytest.raises(exceptions.UbuntuProError) as err: - args.service = service - action_disable(args, cfg) + first_fake_stdout = io.StringIO() + with contextlib.redirect_stdout(first_fake_stdout): + with mock.patch.object(lock, "lock_data_file"): + args.service = service + action_disable(args, cfg) - assert expected_error.msg == err.value.msg + assert expected_error.msg in first_fake_stdout.getvalue() args.assume_yes = True args.format = "json" - with pytest.raises(SystemExit): - with mock.patch.object( - event, "_event_logger_mode", event_logger.EventLoggerMode.JSON - ): - with mock.patch.object(event, "set_event_mode"): - fake_stdout = io.StringIO() - with contextlib.redirect_stdout(fake_stdout): - main_error_handler(action_disable)(args, cfg) + with mock.patch.object(lock, "lock_data_file"): + fake_stdout = io.StringIO() + with contextlib.redirect_stdout(fake_stdout): + main_error_handler(action_disable)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, @@ -488,9 +488,12 @@ args.command = "disable" if root: expected_error = expected_error_template.format( - valid_service="esm-infra" + valid_service="esm-infra", operation="disable" ) - expected_info = {"valid_service": "esm-infra"} + expected_info = { + "valid_service": "esm-infra", + "operation": "disable", + } else: expected_error = expected_error_template expected_info = None @@ -532,12 +535,14 @@ expected["errors"][0]["additional_info"] = expected_info assert expected == json.loads(fake_stdout.getvalue()) + @mock.patch("uaclient.lock.check_lock_info") @mock.patch("time.sleep") @mock.patch("uaclient.system.subp") def test_lock_file_exists( self, m_subp, m_sleep, + m_check_lock_info, FakeConfig, event, ): @@ -547,11 +552,11 @@ expected_error = messages.E_LOCK_HELD_ERROR.format( lock_request="pro disable", lock_holder="pro enable", pid="123" ) - cfg.write_cache("lock", "123:pro enable") + m_check_lock_info.return_value = (123, "pro enable") with pytest.raises(exceptions.LockHeldError) as err: args.service = ["esm-infra"] action_disable(args, cfg) - assert [mock.call(["ps", "123"])] * 12 == m_subp.call_args_list + assert 12 == m_check_lock_info.call_count assert expected_error.msg == err.value.msg args.assume_yes = True @@ -621,17 +626,25 @@ } assert expected == json.loads(fake_stdout.getvalue()) - def test_purge_assume_yes_incompatible(self, capsys): + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) + @mock.patch("uaclient.cli.cli_util._is_attached") + def test_purge_assume_yes_incompatible( + self, m_is_attached, _m_check_lock_info, capsys + ): cfg = mock.MagicMock() args_mock = mock.MagicMock() args_mock.service = "test" args_mock.assume_yes = True args_mock.purge = True + m_is_attached.return_value = mock.MagicMock( + is_attached=True, + contract_status="active", + contract_remaining_days=100, + ) + with pytest.raises(SystemExit): - with mock.patch.object( - cfg, "check_lock_info", return_value=(-1, "") - ): + with mock.patch.object(lock, "lock_data_file"): main_error_handler(action_disable)(args_mock, cfg) _out, err = capsys.readouterr() @@ -642,3 +655,160 @@ ).msg in err.strip() ) + + +class TestPromptForDependencyHandling: + @pytest.mark.parametrize( + [ + "service", + "all_dependencies", + "enabled_service_names", + "called_name", + "service_title", + "prompt_side_effects", + "expected_prompts", + "expected_raise", + ], + [ + # no dependencies + ( + "one", + [ + ServiceWithDependencies( + name="two", incompatible_with=[], depends_on=[] + ) + ], + [], + "one", + "One", + [], + [], + does_not_raise(), + ), + # required by "two", but two not enabled + ( + "one", + [ + ServiceWithDependencies( + name="two", + incompatible_with=[], + depends_on=[ + ServiceWithReason( + name="one", reason=mock.MagicMock() + ) + ], + ) + ], + [], + "one", + "One", + [], + [], + does_not_raise(), + ), + # required by "two", two enabled, successful prompt + ( + "one", + [ + ServiceWithDependencies( + name="two", + incompatible_with=[], + depends_on=[ + ServiceWithReason( + name="one", reason=mock.MagicMock() + ) + ], + ) + ], + ["two"], + "one", + "One", + [True], + [mock.call(msg=mock.ANY)], + does_not_raise(), + ), + # required by "two", two enabled, denied prompt + ( + "one", + [ + ServiceWithDependencies( + name="two", + incompatible_with=[], + depends_on=[ + ServiceWithReason( + name="one", reason=mock.MagicMock() + ) + ], + ) + ], + ["two"], + "one", + "One", + [False], + [mock.call(msg=mock.ANY)], + pytest.raises(exceptions.DependentServiceStopsDisable), + ), + # required by "two" and "three", three enabled, success + ( + "one", + [ + ServiceWithDependencies( + name="two", + incompatible_with=[], + depends_on=[ + ServiceWithReason( + name="one", reason=mock.MagicMock() + ) + ], + ), + ServiceWithDependencies( + name="three", + incompatible_with=[], + depends_on=[ + ServiceWithReason( + name="one", reason=mock.MagicMock() + ) + ], + ), + ], + ["three"], + "one", + "One", + [True], + [mock.call(msg=mock.ANY)], + does_not_raise(), + ), + ], + ) + @mock.patch("uaclient.entitlements.get_title") + @mock.patch("uaclient.util.prompt_for_confirmation") + def test_prompt_for_dependency_handling( + self, + m_prompt_for_confirmation, + m_entitlement_get_title, + service, + all_dependencies, + enabled_service_names, + called_name, + service_title, + prompt_side_effects, + expected_prompts, + expected_raise, + FakeConfig, + ): + m_entitlement_get_title.side_effect = ( + lambda cfg, name, variant="": name.title() + ) + m_prompt_for_confirmation.side_effect = prompt_side_effects + + with expected_raise: + prompt_for_dependency_handling( + FakeConfig(), + service, + all_dependencies, + enabled_service_names, + called_name, + service_title, + ) + + assert expected_prompts == m_prompt_for_confirmation.call_args_list diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_enable.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_enable.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_enable.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_enable.py 2024-05-10 17:07:05.000000000 +0000 @@ -6,12 +6,20 @@ import mock import pytest -from uaclient import entitlements, event_logger, exceptions, messages -from uaclient.cli import action_enable, main, main_error_handler +from uaclient import entitlements, event_logger, exceptions, lock, messages +from uaclient.api.u.pro.services.dependencies.v1 import ( + ServiceWithDependencies, + ServiceWithReason, +) +from uaclient.api.u.pro.status.enabled_services.v1 import EnabledService +from uaclient.cli import main, main_error_handler +from uaclient.cli.enable import action_enable, prompt_for_dependency_handling from uaclient.entitlements.entitlement_status import ( CanEnableFailure, CanEnableFailureReason, ) +from uaclient.files.user_config_file import UserConfigData +from uaclient.testing.helpers import does_not_raise HELP_OUTPUT = """\ usage: pro enable [] [flags] @@ -37,15 +45,23 @@ """ +@mock.patch( + "uaclient.files.user_config_file.UserConfigFileObject.public_config", + new_callable=mock.PropertyMock, + return_value=UserConfigData(), +) +@mock.patch("uaclient.contract.UAContractClient.update_activity_token") @mock.patch("uaclient.contract.refresh") class TestActionEnable: - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch("uaclient.cli.contract.get_available_resources") def test_enable_help( self, _m_resources, _m_setup_logging, _refresh, + _m_update_activity_token, + _m_public_config, capsys, FakeConfig, ): @@ -59,7 +75,7 @@ out, _err = capsys.readouterr() assert HELP_OUTPUT == out - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch("uaclient.util.we_are_currently_root", return_value=False) @mock.patch("uaclient.cli.contract.get_available_resources") def test_non_root_users_are_rejected( @@ -68,6 +84,8 @@ _refresh, we_are_currently_root, m_setup_logging, + _m_update_activity_token, + _m_public_config, capsys, event, FakeConfig, @@ -131,53 +149,50 @@ } assert expected == json.loads(capsys.readouterr()[0]) + @mock.patch("uaclient.lock.check_lock_info") @mock.patch("time.sleep") @mock.patch("uaclient.system.subp") def test_lock_file_exists( self, m_subp, m_sleep, + m_check_lock_info, _refresh, + _m_update_activity_token, + _m_public_config, capsys, event, FakeConfig, ): """Check inability to enable if operation holds lock file.""" cfg = FakeConfig.for_attached_machine() - cfg.write_cache("lock", "123:pro disable") + m_check_lock_info.return_value = (123, "pro disable") args = mock.MagicMock() with pytest.raises(exceptions.LockHeldError) as err: action_enable(args, cfg=cfg) - assert [mock.call(["ps", "123"])] * 12 == m_subp.call_args_list + assert 12 == m_check_lock_info.call_count - expected_message = messages.E_LOCK_HELD_ERROR.format( + expected_msg = messages.E_LOCK_HELD_ERROR.format( lock_request="pro enable", lock_holder="pro disable", pid="123" ) - assert expected_message.msg == err.value.msg + assert expected_msg.msg == err.value.msg with pytest.raises(SystemExit): with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): - with mock.patch.object( - cfg, "check_lock_info" - ) as m_check_lock_info: - m_check_lock_info.return_value = (1, "lock_holder") - main_error_handler(action_enable)(args, cfg) + main_error_handler(action_enable)(args, cfg) - expected_msg = messages.E_LOCK_HELD_ERROR.format( - lock_request="pro enable", lock_holder="lock_holder", pid=1 - ) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "additional_info": { - "lock_holder": "lock_holder", + "lock_holder": "pro disable", "lock_request": "pro enable", - "pid": 1, + "pid": 123, }, "message": expected_msg.msg, "message_code": expected_msg.name, @@ -204,6 +219,8 @@ self, m_we_are_currently_root, _refresh, + _m_update_activity_token, + _m_public_config, root, expected_error_template, capsys, @@ -221,10 +238,11 @@ if root: expected_error = expected_error_template.format( - valid_service="esm-infra" + valid_service="esm-infra", operation="enable" ) expected_info = { "valid_service": "esm-infra", + "operation": "enable", } else: expected_error = expected_error_template @@ -268,11 +286,15 @@ (False, messages.E_NONROOT_USER), ], ) + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.util.we_are_currently_root") def test_invalid_service_error_message( self, m_we_are_currently_root, + _m_check_lock_info, _refresh, + _m_update_activity_token, + _m_public_config, root, expected_error_template, is_attached, @@ -309,8 +331,15 @@ args.command = "enable" args.access_only = False - with pytest.raises(exceptions.UbuntuProError) as err: - action_enable(args, cfg) + fake_stdout = io.StringIO() + if root and is_attached: + expected_err = does_not_raise() + else: + expected_err = pytest.raises(exceptions.UbuntuProError) + with expected_err as err: + with mock.patch.object(lock, "lock_data_file"): + with contextlib.redirect_stdout(fake_stdout): + action_enable(args, cfg=cfg) if root: expected_error = expected_error_template.format( @@ -327,15 +356,25 @@ expected_error = expected_error_template expected_info = None - assert expected_error.msg == err.value.msg + if root and is_attached: + assert expected_error.msg in fake_stdout.getvalue() + else: + assert expected_error.msg == err.value.msg - with pytest.raises(SystemExit): + args.assume_yes = True + args.format = "json" + if root and is_attached: + expected_err = does_not_raise() + else: + expected_err = pytest.raises(SystemExit) + with expected_err: with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): - fake_stdout = io.StringIO() - with contextlib.redirect_stdout(fake_stdout): - main_error_handler(action_enable)(args, cfg) + with mock.patch.object(lock, "lock_data_file"): + fake_stdout = io.StringIO() + with contextlib.redirect_stdout(fake_stdout): + main_error_handler(action_enable)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, @@ -369,6 +408,8 @@ self, m_we_are_currently_root, _refresh, + _m_update_activity_token, + _m_public_config, root, expected_error_template, event, @@ -433,17 +474,19 @@ assert expected == json.loads(fake_stdout.getvalue()) @pytest.mark.parametrize("assume_yes", (True, False)) - @mock.patch( - "uaclient.cli.contract.UAContractClient.update_activity_token", - ) + @mock.patch("uaclient.files.state_files.status_cache_file.write") + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.status.get_available_resources", return_value={}) @mock.patch("uaclient.entitlements.valid_services") def test_assume_yes_passed_to_service_init( self, m_valid_services, _m_get_available_resources, - _m_update_activity_token, + _m_check_lock_info, + _m_status_cache_file, m_refresh, + _m_update_activity_token, + _m_public_config, assume_yes, FakeConfig, ): @@ -466,7 +509,8 @@ "uaclient.entitlements.entitlement_factory", return_value=m_entitlement_cls, ): - action_enable(args, cfg) + with mock.patch.object(lock, "lock_data_file"): + action_enable(args, cfg) assert [ mock.call( @@ -476,9 +520,11 @@ called_name="testitlement", access_only=False, extra_args=None, - ) + ), ] == m_entitlement_cls.call_args_list + @mock.patch("uaclient.files.state_files.status_cache_file.write") + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.status.get_available_resources", return_value={}) @mock.patch("uaclient.entitlements.entitlement_factory") @mock.patch("uaclient.entitlements.valid_services") @@ -487,34 +533,44 @@ m_valid_services, m_entitlement_factory, _m_get_available_resources, + _m_check_lock_info, _m_refresh, + _m_status_cache_file, + _m_update_activity_token, + _m_public_config, event, FakeConfig, ): expected_error_tmpl = messages.E_INVALID_SERVICE_OP_FAILURE - m_ent1_cls = mock.Mock() + m_ent1_cls = mock.MagicMock() m_ent1_obj = m_ent1_cls.return_value + type(m_ent1_obj).title = mock.PropertyMock(return_value="Ent1") m_ent1_obj.enable.return_value = (False, None) + m_ent1_obj._check_for_reboot.return_value = False - m_ent2_cls = mock.Mock() + m_ent2_cls = mock.MagicMock() m_ent2_cls.name = "ent2" m_ent2_is_beta = mock.PropertyMock(return_value=True) type(m_ent2_cls).is_beta = m_ent2_is_beta m_ent2_obj = m_ent2_cls.return_value + type(m_ent2_obj).title = mock.PropertyMock(return_value="Ent2") + m_ent2_obj._check_for_reboot.return_value = False m_ent2_obj.enable.return_value = ( False, CanEnableFailure(CanEnableFailureReason.IS_BETA), ) - m_ent3_cls = mock.Mock() + m_ent3_cls = mock.MagicMock() m_ent3_cls.name = "ent3" m_ent3_is_beta = mock.PropertyMock(return_value=False) type(m_ent3_cls).is_beta = m_ent3_is_beta m_ent3_obj = m_ent3_cls.return_value + type(m_ent3_obj).title = mock.PropertyMock(return_value="Ent3") m_ent3_obj.enable.return_value = (True, None) + m_ent3_obj._check_for_reboot.return_value = False - def factory_side_effect(cfg, name, variant): + def factory_side_effect(cfg, name, variant=""): if name == "ent2": return m_ent2_cls if name == "ent3": @@ -533,12 +589,16 @@ args_mock.beta = False args_mock.variant = "" - expected_msg = "One moment, checking your subscription first\n" + expected_msg = ( + "One moment, checking your subscription first\n" + "Could not enable Ent2.\n" + "Ent3 enabled\n" + ) - with pytest.raises(exceptions.UbuntuProError) as err: + with mock.patch.object(lock, "lock_data_file"): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): - action_enable(args_mock, cfg) + action_enable(args_mock, cfg=cfg) service_msg = ( "Try " @@ -550,8 +610,9 @@ invalid_service="ent1, ent2", service_msg=service_msg, ) - assert expected_error.msg == err.value.msg - assert expected_msg == fake_stdout.getvalue() + assert ( + expected_msg + expected_error.msg + "\n" == fake_stdout.getvalue() + ) for m_ent_cls in [m_ent2_cls, m_ent3_cls]: assert [ @@ -562,23 +623,22 @@ called_name=m_ent_cls.name, access_only=False, extra_args=None, - ) + ), ] == m_ent_cls.call_args_list - expected_enable_call = mock.call() + expected_enable_call = mock.call(mock.ANY) for m_ent in [m_ent2_obj, m_ent3_obj]: assert [expected_enable_call] == m_ent.enable.call_args_list assert 0 == m_ent1_obj.call_count event.reset() - with pytest.raises(SystemExit): - with mock.patch.object( - event, "_event_logger_mode", event_logger.EventLoggerMode.JSON - ): - fake_stdout = io.StringIO() - with contextlib.redirect_stdout(fake_stdout): - main_error_handler(action_enable)(args_mock, cfg) + args_mock.assume_yes = True + args_mock.format = "json" + with mock.patch.object(lock, "lock_data_file"): + fake_stdout = io.StringIO() + with contextlib.redirect_stdout(fake_stdout): + main_error_handler(action_enable)(args_mock, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, @@ -604,6 +664,8 @@ assert expected == json.loads(fake_stdout.getvalue()) @pytest.mark.parametrize("beta_flag", ((False), (True))) + @mock.patch("uaclient.files.state_files.status_cache_file.write") + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.status.get_available_resources", return_value={}) @mock.patch("uaclient.entitlements.entitlement_factory") @mock.patch("uaclient.entitlements.valid_services") @@ -612,34 +674,44 @@ m_valid_services, m_entitlement_factory, _m_get_available_resources, + _m_check_lock_info, + _m_status_cache_file, _m_refresh, + _m_update_activity_token, + _m_public_config, beta_flag, event, FakeConfig, ): expected_error_tmpl = messages.E_INVALID_SERVICE_OP_FAILURE - m_ent1_cls = mock.Mock() + m_ent1_cls = mock.MagicMock() m_ent1_obj = m_ent1_cls.return_value + type(m_ent1_obj).title = mock.PropertyMock(return_value="Ent1") m_ent1_obj.enable.return_value = (False, None) + m_ent1_obj._check_for_reboot.return_value = False - m_ent2_cls = mock.Mock() + m_ent2_cls = mock.MagicMock() m_ent2_cls.name = "ent2" m_ent2_is_beta = mock.PropertyMock(return_value=True) type(m_ent2_cls)._is_beta = m_ent2_is_beta m_ent2_obj = m_ent2_cls.return_value + type(m_ent2_obj).title = mock.PropertyMock(return_value="Ent2") + m_ent2_obj._check_for_reboot.return_value = False failure_reason = CanEnableFailure(CanEnableFailureReason.IS_BETA) if beta_flag: m_ent2_obj.enable.return_value = (True, None) else: m_ent2_obj.enable.return_value = (False, failure_reason) - m_ent3_cls = mock.Mock() + m_ent3_cls = mock.MagicMock() m_ent3_cls.name = "ent3" m_ent3_is_beta = mock.PropertyMock(return_value=False) type(m_ent3_cls)._is_beta = m_ent3_is_beta m_ent3_obj = m_ent3_cls.return_value + type(m_ent3_obj).title = mock.PropertyMock(return_value="Ent3") m_ent3_obj.enable.return_value = (True, None) + m_ent3_obj._check_for_reboot.return_value = False cfg = FakeConfig.for_attached_machine() assume_yes = False @@ -650,7 +722,7 @@ args_mock.beta = beta_flag args_mock.variant = "" - def factory_side_effect(cfg, name, variant): + def factory_side_effect(cfg, name, variant=""): if name == "ent2": return m_ent2_cls if name == "ent3": @@ -666,7 +738,18 @@ m_valid_services.side_effect = valid_services_side_effect - expected_msg = "One moment, checking your subscription first\n" + if beta_flag: + expected_msg = ( + "One moment, checking your subscription first\n" + "Ent2 enabled\n" + "Ent3 enabled\n" + ) + else: + expected_msg = ( + "One moment, checking your subscription first\n" + "Could not enable Ent2.\n" + "Ent3 enabled\n" + ) not_found_name = "ent1" mock_ent_list = [m_ent3_cls] mock_obj_list = [m_ent3_obj] @@ -687,18 +770,19 @@ ) ) - with pytest.raises(exceptions.UbuntuProError) as err: + with mock.patch.object(lock, "lock_data_file"): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): - action_enable(args_mock, cfg) + action_enable(args_mock, cfg=cfg) expected_error = expected_error_tmpl.format( operation="enable", invalid_service=not_found_name, service_msg=service_msg, ) - assert expected_error.msg == err.value.msg - assert expected_msg == fake_stdout.getvalue() + assert ( + expected_msg + expected_error.msg + "\n" == fake_stdout.getvalue() + ) for m_ent_cls in mock_ent_list: assert [ @@ -709,23 +793,22 @@ called_name=m_ent_cls.name, access_only=False, extra_args=None, - ) + ), ] == m_ent_cls.call_args_list - expected_enable_call = mock.call() + expected_enable_call = mock.call(mock.ANY) for m_ent in mock_obj_list: assert [expected_enable_call] == m_ent.enable.call_args_list assert 0 == m_ent1_obj.call_count event.reset() - with pytest.raises(SystemExit): - with mock.patch.object( - event, "_event_logger_mode", event_logger.EventLoggerMode.JSON - ): - fake_stdout = io.StringIO() - with contextlib.redirect_stdout(fake_stdout): - main_error_handler(action_enable)(args_mock, cfg=cfg) + args_mock.assume_yes = True + args_mock.format = "json" + with mock.patch.object(lock, "lock_data_file"): + fake_stdout = io.StringIO() + with contextlib.redirect_stdout(fake_stdout): + main_error_handler(action_enable)(args_mock, cfg=cfg) expected_failed_services = ["ent1", "ent2"] if beta_flag: @@ -756,21 +839,24 @@ } assert expected == json.loads(fake_stdout.getvalue()) - @mock.patch( - "uaclient.cli.contract.UAContractClient.update_activity_token", - ) + @mock.patch("uaclient.files.state_files.status_cache_file.write") + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.status.get_available_resources", return_value={}) def test_print_message_when_can_enable_fails( self, _m_get_available_resources, - _m_update_activity_token, + _m_check_lock_info, + _m_status_cache_file, _m_refresh, + _m_update_activity_token, + _m_public_config, event, FakeConfig, ): - m_entitlement_cls = mock.Mock() + m_entitlement_cls = mock.MagicMock() type(m_entitlement_cls).is_beta = mock.PropertyMock(return_value=False) m_entitlement_obj = m_entitlement_cls.return_value + type(m_entitlement_obj).title = mock.PropertyMock(return_value="Title") m_entitlement_obj.enable.return_value = ( False, CanEnableFailure( @@ -792,26 +878,29 @@ ), mock.patch( "uaclient.entitlements.valid_services", return_value=["ent1"] ): - fake_stdout = io.StringIO() - with contextlib.redirect_stdout(fake_stdout): - action_enable(args_mock, cfg) + with mock.patch.object(lock, "lock_data_file"): + fake_stdout = io.StringIO() + with contextlib.redirect_stdout(fake_stdout): + action_enable(args_mock, cfg=cfg) assert ( "One moment, checking your subscription first\nmsg\n" - == fake_stdout.getvalue() - ) + "Could not enable Title.\n" + ) == fake_stdout.getvalue() + + args_mock.assume_yes = True + args_mock.format = "json" with mock.patch( "uaclient.entitlements.entitlement_factory", return_value=m_entitlement_cls, ), mock.patch( "uaclient.entitlements.valid_services", return_value=["ent1"] - ), mock.patch.object( - event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): - fake_stdout = io.StringIO() - with contextlib.redirect_stdout(fake_stdout): - ret = action_enable(args_mock, cfg=cfg) + with mock.patch.object(lock, "lock_data_file"): + fake_stdout = io.StringIO() + with contextlib.redirect_stdout(fake_stdout): + ret = action_enable(args_mock, cfg=cfg) expected_ret = 1 expected = { @@ -837,9 +926,13 @@ "service, beta", ((["bogus"], False), (["bogus"], True), (["bogus1", "bogus2"], False)), ) + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) def test_invalid_service_names( self, + _m_check_lock_info, _m_refresh, + _m_update_activity_token, + _m_public_config, service, beta, event, @@ -854,12 +947,10 @@ args_mock.beta = beta args_mock.access_only = False - with pytest.raises(exceptions.UbuntuProError) as err: + with mock.patch.object(lock, "lock_data_file"): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): - action_enable(args_mock, cfg) - - assert expected_msg == fake_stdout.getvalue() + action_enable(args_mock, cfg=cfg) service_names = entitlements.valid_services(cfg=cfg, allow_beta=beta) ent_str = "Try " + ", ".join(service_names) + "." @@ -876,15 +967,17 @@ invalid_service=", ".join(sorted(service)), service_msg=service_msg, ) - assert expected_error.msg == err.value.msg + assert ( + expected_msg + expected_error.msg + "\n" == fake_stdout.getvalue() + ) - with pytest.raises(SystemExit): - with mock.patch.object( - event, "_event_logger_mode", event_logger.EventLoggerMode.JSON - ): - fake_stdout = io.StringIO() - with contextlib.redirect_stdout(fake_stdout): - main_error_handler(action_enable)(args_mock, cfg) + args_mock.assume_yes = True + args_mock.format = "json" + + with mock.patch.object(lock, "lock_data_file"): + fake_stdout = io.StringIO() + with contextlib.redirect_stdout(fake_stdout): + main_error_handler(action_enable)(args_mock, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, @@ -910,33 +1003,35 @@ assert expected == json.loads(fake_stdout.getvalue()) @pytest.mark.parametrize("allow_beta", ((True), (False))) - @mock.patch( - "uaclient.cli.contract.UAContractClient.update_activity_token", - ) + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.status.get_available_resources", return_value={}) @mock.patch("uaclient.status.status") def test_entitlement_instantiated_and_enabled( self, m_status, _m_get_available_resources, - m_update_activity_token, + _m_check_lock_info, _m_refresh, + m_update_activity_token, + _m_public_config, allow_beta, event, FakeConfig, ): - m_entitlement_cls = mock.Mock() + m_entitlement_cls = mock.MagicMock() m_entitlement_obj = m_entitlement_cls.return_value m_entitlement_obj.enable.return_value = (True, None) + m_entitlement_obj._check_for_reboot.return_value = False cfg = FakeConfig.for_attached_machine() args_mock = mock.MagicMock() args_mock.access_only = False - args_mock.assume_yes = False + args_mock.assume_yes = True args_mock.beta = allow_beta args_mock.service = ["testitlement"] args_mock.variant = "" + args_mock.format = "json" with mock.patch( "uaclient.entitlements.entitlement_factory", @@ -945,21 +1040,22 @@ "uaclient.entitlements.valid_services", return_value=["testitlement"], ): - ret = action_enable(args_mock, cfg) + with mock.patch.object(lock, "lock_data_file"): + ret = action_enable(args_mock, cfg=cfg) assert [ mock.call( cfg, - assume_yes=False, + assume_yes=True, allow_beta=allow_beta, called_name="testitlement", access_only=False, extra_args=None, - ) + ), ] == m_entitlement_cls.call_args_list m_entitlement = m_entitlement_cls.return_value - expected_enable_call = mock.call() + expected_enable_call = mock.call(mock.ANY) expected_ret = 0 assert [expected_enable_call] == m_entitlement.enable.call_args_list assert expected_ret == ret @@ -972,12 +1068,11 @@ ), mock.patch( "uaclient.entitlements.valid_services", return_value=["testitlement"], - ), mock.patch.object( - event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): - fake_stdout = io.StringIO() - with contextlib.redirect_stdout(fake_stdout): - ret = action_enable(args_mock, cfg=cfg) + with mock.patch.object(lock, "lock_data_file"): + fake_stdout = io.StringIO() + with contextlib.redirect_stdout(fake_stdout): + ret = action_enable(args_mock, cfg=cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, @@ -992,7 +1087,11 @@ assert expected_ret == ret def test_format_json_fails_when_assume_yes_flag_not_used( - self, _m_get_available_resources, event + self, + _m_get_available_resources, + _m_update_activity_token, + _m_public_config, + event, ): cfg = mock.MagicMock() args_mock = mock.MagicMock() @@ -1026,8 +1125,14 @@ } assert expected == json.loads(fake_stdout.getvalue()) + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) def test_access_only_cannot_be_used_together_with_variant( - self, _m_get_available_resources, FakeConfig + self, + _m_check_lock_info, + _m_get_available_resources, + _m_update_activity_token, + _m_public_config, + FakeConfig, ): cfg = FakeConfig.for_attached_machine() args_mock = mock.MagicMock() @@ -1035,4 +1140,357 @@ args_mock.variant = "variant" with pytest.raises(exceptions.InvalidOptionCombination): - action_enable(args_mock, cfg) + with mock.patch.object(lock, "lock_data_file"): + action_enable(args_mock, cfg) + + +class TestPromptForDependencyHandling: + @pytest.mark.parametrize( + [ + "service", + "all_dependencies", + "enabled_services", + "called_name", + "service_title", + "variant", + "cfg_block_disable_on_enable", + "prompt_side_effects", + "expected_prompts", + "expected_raise", + ], + [ + # no dependencies + ( + "one", + [ + ServiceWithDependencies( + name="one", incompatible_with=[], depends_on=[] + ) + ], + [], + "one", + "One", + "", + False, + [], + [], + does_not_raise(), + ), + # incompatible with "two", but two not enabled + ( + "one", + [ + ServiceWithDependencies( + name="one", + incompatible_with=[ + ServiceWithReason( + name="two", reason=mock.MagicMock() + ) + ], + depends_on=[], + ) + ], + [], + "one", + "One", + "", + False, + [], + [], + does_not_raise(), + ), + # incompatible with "two", two enabled, successful prompt + ( + "one", + [ + ServiceWithDependencies( + name="one", + incompatible_with=[ + ServiceWithReason( + name="two", reason=mock.MagicMock() + ) + ], + depends_on=[], + ) + ], + [EnabledService(name="two")], + "one", + "One", + "", + False, + [True], + [mock.call(msg=mock.ANY)], + does_not_raise(), + ), + # incompatible with "two", two enabled, cfg denies + ( + "one", + [ + ServiceWithDependencies( + name="one", + incompatible_with=[ + ServiceWithReason( + name="two", reason=mock.MagicMock() + ) + ], + depends_on=[], + ) + ], + [EnabledService(name="two")], + "one", + "One", + "", + True, + [], + [], + pytest.raises(exceptions.IncompatibleServiceStopsEnable), + ), + # incompatible with "two", two enabled, denied prompt + ( + "one", + [ + ServiceWithDependencies( + name="one", + incompatible_with=[ + ServiceWithReason( + name="two", reason=mock.MagicMock() + ) + ], + depends_on=[], + ) + ], + [EnabledService(name="two")], + "one", + "One", + "", + False, + [False], + [mock.call(msg=mock.ANY)], + pytest.raises(exceptions.IncompatibleServiceStopsEnable), + ), + # incompatible with "two" and "three", three enabled, success + ( + "one", + [ + ServiceWithDependencies( + name="one", + incompatible_with=[ + ServiceWithReason( + name="two", reason=mock.MagicMock() + ), + ServiceWithReason( + name="three", reason=mock.MagicMock() + ), + ], + depends_on=[], + ) + ], + [EnabledService(name="three")], + "one", + "One", + "", + False, + [True], + [mock.call(msg=mock.ANY)], + does_not_raise(), + ), + # depends on "two", but two already enabled + ( + "one", + [ + ServiceWithDependencies( + name="one", + incompatible_with=[], + depends_on=[ + ServiceWithReason( + name="two", reason=mock.MagicMock() + ) + ], + ) + ], + [EnabledService(name="two")], + "one", + "One", + "", + False, + [], + [], + does_not_raise(), + ), + # depends on "two", two not enabled, successful prompt + ( + "one", + [ + ServiceWithDependencies( + name="one", + incompatible_with=[], + depends_on=[ + ServiceWithReason( + name="two", reason=mock.MagicMock() + ) + ], + ) + ], + [], + "one", + "One", + "", + False, + [True], + [mock.call(msg=mock.ANY)], + does_not_raise(), + ), + # depends on "two", two not enabled, denied prompt + ( + "one", + [ + ServiceWithDependencies( + name="one", + incompatible_with=[], + depends_on=[ + ServiceWithReason( + name="two", reason=mock.MagicMock() + ) + ], + ) + ], + [], + "one", + "One", + "", + False, + [False], + [mock.call(msg=mock.ANY)], + pytest.raises(exceptions.RequiredServiceStopsEnable), + ), + # depends on "two" and "three", three not enabled, success prompt + ( + "one", + [ + ServiceWithDependencies( + name="one", + incompatible_with=[], + depends_on=[ + ServiceWithReason( + name="two", reason=mock.MagicMock() + ), + ServiceWithReason( + name="three", reason=mock.MagicMock() + ), + ], + ) + ], + [EnabledService(name="two")], + "one", + "One", + "", + False, + [True], + [mock.call(msg=mock.ANY)], + does_not_raise(), + ), + # a variant is enabled, second variant is being enabled + ( + "two", + [], + [ + EnabledService( + name="two", variant_enabled=True, variant_name="one" + ) + ], + "two", + "Two", + "two", + False, + [False], + [mock.call(msg=mock.ANY)], + pytest.raises(exceptions.IncompatibleServiceStopsEnable), + ), + # lots of stuff + ( + "one", + [ + ServiceWithDependencies( + name="one", + incompatible_with=[ + ServiceWithReason( + name="two", reason=mock.MagicMock() + ), + ServiceWithReason( + name="three", reason=mock.MagicMock() + ), + ServiceWithReason( + name="four", reason=mock.MagicMock() + ), + ], + depends_on=[ + ServiceWithReason( + name="five", reason=mock.MagicMock() + ), + ServiceWithReason( + name="six", reason=mock.MagicMock() + ), + ServiceWithReason( + name="seven", reason=mock.MagicMock() + ), + ], + ) + ], + [ + EnabledService(name="two"), + EnabledService(name="four"), + EnabledService(name="six"), + ], + "one", + "One", + "", + False, + [True, True, True, True], + [ + mock.call(msg=mock.ANY), + mock.call(msg=mock.ANY), + mock.call(msg=mock.ANY), + mock.call(msg=mock.ANY), + ], + does_not_raise(), + ), + ], + ) + @mock.patch("uaclient.entitlements.get_title") + @mock.patch("uaclient.util.prompt_for_confirmation") + @mock.patch("uaclient.util.is_config_value_true") + def test_prompt_for_dependency_handling( + self, + m_is_config_value_true, + m_prompt_for_confirmation, + m_entitlement_get_title, + service, + all_dependencies, + enabled_services, + called_name, + service_title, + variant, + cfg_block_disable_on_enable, + prompt_side_effects, + expected_prompts, + expected_raise, + FakeConfig, + ): + m_entitlement_get_title.side_effect = ( + lambda cfg, name, variant=variant: name.title() + ) + m_is_config_value_true.return_value = cfg_block_disable_on_enable + m_prompt_for_confirmation.side_effect = prompt_side_effects + + with expected_raise: + prompt_for_dependency_handling( + FakeConfig(), + service, + all_dependencies, + enabled_services, + called_name, + variant, + service_title, + ) + + assert expected_prompts == m_prompt_for_confirmation.call_args_list diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_fix.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_fix.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_fix.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_fix.py 2024-04-23 13:37:02.000000000 +0000 @@ -14,6 +14,7 @@ AptUpgradeData, AttachData, EnableData, + FailUpdatingESMCacheData, FixPlanAptUpgradeStep, FixPlanAttachStep, FixPlanEnableStep, @@ -22,6 +23,7 @@ FixPlanNoOpStep, FixPlanResult, FixPlanUSNResult, + FixPlanWarningFailUpdatingESMCache, FixPlanWarningPackageCannotBeInstalled, FixPlanWarningSecurityIssueNotFixed, NoOpData, @@ -80,7 +82,7 @@ class TestActionFix: - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch("uaclient.cli.contract.get_available_resources") def test_fix_help( self, _m_resources, _m_setup_logging, capsys, FakeConfig @@ -823,6 +825,72 @@ ) + colorize_commands( [["apt update && apt install --only-upgrade" " -y pkg2"]] + ) + + "\n\n" + + "1 package is still affected: pkg1" + + "\n" + + "{check} USN-### is not resolved.\n".format( + check=messages.FAIL_X + ), + FixStatus.SYSTEM_STILL_VULNERABLE, + [ + UnfixedPackage( + pkg="pkg1", + unfixed_reason="Cannot install package pkg1 version 2.0", # noqa + ), + ], + ), + ( + FixPlanResult( + title="USN-###", + description="test", + expected_status=FixStatus.SYSTEM_STILL_VULNERABLE.value.msg, # noqa + affected_packages=["pkg1", "pkg2"], + plan=[ + FixPlanAptUpgradeStep( + data=AptUpgradeData( + binary_packages=["pkg2"], + source_packages=["pkg2"], + pocket=STANDARD_UPDATES_POCKET, + ), + order=3, + ), + ], + warnings=[ + FixPlanWarningFailUpdatingESMCache( + data=FailUpdatingESMCacheData( + title="Error msg", + code="error-code", + ), + order=1, + ), + FixPlanWarningPackageCannotBeInstalled( + data=PackageCannotBeInstalledData( + binary_package="pkg1", + binary_package_version="2.0", + source_package="pkg1", + related_source_packages=["pkg1", "pkg2"], + pocket=STANDARD_UPDATES_POCKET, + ), + order=2, + ), + ], + error=None, + additional_data=None, + ), + False, + (None, None), + textwrap.dedent( + """\ + 2 affected source packages are installed: pkg1, pkg2 + WARNING: Failed to update ESM cache - package availability may be inaccurate + (1/2, 2/2) pkg1, pkg2: + A fix is available in Ubuntu standard updates. + - Cannot install package pkg1 version 2.0 + """ # noqa + ) + + colorize_commands( + [["apt update && apt install --only-upgrade" " -y pkg2"]] ) + "\n\n" + "1 package is still affected: pkg1" diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_reboot_required.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_reboot_required.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_reboot_required.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_reboot_required.py 2024-04-23 13:37:02.000000000 +0000 @@ -22,7 +22,7 @@ class TestActionRebootRequired: - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") def test_enable_help(self, _m_setup_logging, capsys, FakeConfig): with pytest.raises(SystemExit): with mock.patch( diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_refresh.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_refresh.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_refresh.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_refresh.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,7 +1,7 @@ import mock import pytest -from uaclient import exceptions, messages +from uaclient import exceptions, lock, messages from uaclient.cli import action_refresh, main from uaclient.files.notices import Notice @@ -28,7 +28,7 @@ class TestActionRefresh: - @mock.patch("uaclient.cli.setup_logging") + @mock.patch("uaclient.log.setup_cli_logging") @mock.patch("uaclient.cli.contract.get_available_resources") def test_refresh_help( self, _m_resources, _m_setup_logging, capsys, FakeConfig @@ -57,8 +57,10 @@ "target, expect_unattached_error", [(None, True), ("contract", True), ("config", False)], ) + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) def test_not_attached_errors( self, + _m_check_lock_info, target, expect_unattached_error, FakeConfig, @@ -69,26 +71,31 @@ cfg.user_config.update_messaging_timer = 0 cfg.user_config.metering_timer = 0 - if expect_unattached_error: - with pytest.raises(exceptions.UnattachedError): + with mock.patch.object(lock, "lock_data_file"): + if expect_unattached_error: + with pytest.raises(exceptions.UnattachedError): + action_refresh(mock.MagicMock(target=target), cfg=cfg) + else: action_refresh(mock.MagicMock(target=target), cfg=cfg) - else: - action_refresh(mock.MagicMock(target=target), cfg=cfg) + @mock.patch("uaclient.lock.check_lock_info") @mock.patch("time.sleep") @mock.patch("uaclient.system.subp") - def test_lock_file_exists(self, m_subp, m_sleep, FakeConfig): + def test_lock_file_exists( + self, m_subp, m_sleep, m_check_lock_info, FakeConfig + ): """Check inability to refresh if operation holds lock file.""" cfg = FakeConfig().for_attached_machine() - cfg.write_cache("lock", "123:pro disable") + m_check_lock_info.return_value = (123, "pro disable") with pytest.raises(exceptions.LockHeldError) as err: action_refresh(mock.MagicMock(), cfg=cfg) - assert [mock.call(["ps", "123"])] * 12 == m_subp.call_args_list - assert ( - "Unable to perform: pro refresh.\n" - "Operation in progress: pro disable (pid:123)" - ) == err.value.msg + assert 12 == m_check_lock_info.call_count + expected_msg = messages.E_LOCK_HELD_ERROR.format( + lock_request="pro refresh", lock_holder="pro disable", pid=123 + ) + assert expected_msg.msg == err.value.msg + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("logging.exception") @mock.patch("uaclient.contract.refresh") @mock.patch("uaclient.files.notices.NoticesManager.remove") @@ -97,6 +104,7 @@ m_remove_notice, refresh, logging_error, + m_check_log_info, FakeConfig, ): """On failure in request_updates_contract emit an error.""" @@ -107,19 +115,22 @@ cfg = FakeConfig.for_attached_machine() with pytest.raises(exceptions.UbuntuProError) as excinfo: - action_refresh(mock.MagicMock(target="contract"), cfg=cfg) + with mock.patch.object(lock, "lock_data_file"): + action_refresh(mock.MagicMock(target="contract"), cfg=cfg) assert messages.E_REFRESH_CONTRACT_FAILURE.msg == excinfo.value.msg assert [ mock.call("", messages.NOTICE_REFRESH_CONTRACT_WARNING) ] != m_remove_notice.call_args_list + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.contract.refresh") @mock.patch("uaclient.files.notices.NoticesManager.remove") def test_refresh_contract_happy_path( self, m_remove_notice, refresh, + _m_check_lock_info, capsys, FakeConfig, ): @@ -127,7 +138,8 @@ refresh.return_value = True cfg = FakeConfig.for_attached_machine() - ret = action_refresh(mock.MagicMock(target="contract"), cfg=cfg) + with mock.patch.object(lock, "lock_data_file"): + ret = action_refresh(mock.MagicMock(target="contract"), cfg=cfg) assert 0 == ret assert messages.REFRESH_CONTRACT_SUCCESS in capsys.readouterr()[0] @@ -137,16 +149,23 @@ mock.call(Notice.OPERATION_IN_PROGRESS), ] == m_remove_notice.call_args_list + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.cli.update_motd_messages") - def test_refresh_messages_error(self, m_update_motd, FakeConfig): + def test_refresh_messages_error( + self, m_update_motd, _m_check_lock_info, FakeConfig + ): """On failure in update_motd_messages emit an error.""" m_update_motd.side_effect = Exception("test") with pytest.raises(exceptions.UbuntuProError) as excinfo: - action_refresh(mock.MagicMock(target="messages"), cfg=FakeConfig()) + with mock.patch.object(lock, "lock_data_file"): + action_refresh( + mock.MagicMock(target="messages"), cfg=FakeConfig() + ) assert messages.E_REFRESH_MESSAGES_FAILURE.msg == excinfo.value.msg + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.apt_news.update_apt_news") @mock.patch("uaclient.timer.update_messaging.exists", return_value=True) @mock.patch("uaclient.timer.update_messaging.LOG.exception") @@ -159,21 +178,24 @@ log_exception, _m_path, _m_update_apt_news, + _m_check_lock_info, capsys, FakeConfig, ): subp_exc = Exception("test") m_subp.side_effect = [subp_exc, ""] - ret = action_refresh( - mock.MagicMock(target="messages"), cfg=FakeConfig() - ) + with mock.patch.object(lock, "lock_data_file"): + ret = action_refresh( + mock.MagicMock(target="messages"), cfg=FakeConfig() + ) assert 0 == ret assert 1 == log_exception.call_count assert [mock.call(subp_exc)] == log_exception.call_args_list assert messages.REFRESH_MESSAGES_SUCCESS in capsys.readouterr()[0] + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.apt_news.update_apt_news") @mock.patch("uaclient.cli.refresh_motd") @mock.patch("uaclient.cli.update_motd_messages") @@ -182,12 +204,14 @@ m_update_motd, m_refresh_motd, m_update_apt_news, + _m_check_lock_info, capsys, FakeConfig, ): """On success from request_updates_contract root user can refresh.""" cfg = FakeConfig() - ret = action_refresh(mock.MagicMock(target="messages"), cfg=cfg) + with mock.patch.object(lock, "lock_data_file"): + ret = action_refresh(mock.MagicMock(target="messages"), cfg=cfg) assert 0 == ret assert messages.REFRESH_MESSAGES_SUCCESS in capsys.readouterr()[0] @@ -197,6 +221,7 @@ assert 1 == m_refresh_motd.call_count assert 1 == m_update_apt_news.call_count + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("logging.exception") @mock.patch( "uaclient.config.UAConfig.process_config", side_effect=RuntimeError() @@ -205,6 +230,7 @@ self, _m_process_config, _m_logging_error, + _m_check_lock_info, FakeConfig, ): """On failure in process_config emit an error.""" @@ -212,26 +238,31 @@ cfg = FakeConfig.for_attached_machine() with pytest.raises(exceptions.UbuntuProError) as excinfo: - action_refresh(mock.MagicMock(target="config"), cfg=cfg) + with mock.patch.object(lock, "lock_data_file"): + action_refresh(mock.MagicMock(target="config"), cfg=cfg) assert messages.E_REFRESH_CONFIG_FAILURE.msg == excinfo.value.msg + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.config.UAConfig.process_config") def test_refresh_config_happy_path( self, m_process_config, + m_check_lock_info, capsys, FakeConfig, ): """On success from process_config root user gets success message.""" cfg = FakeConfig.for_attached_machine() - ret = action_refresh(mock.MagicMock(target="config"), cfg=cfg) + with mock.patch.object(lock, "lock_data_file"): + ret = action_refresh(mock.MagicMock(target="config"), cfg=cfg) assert 0 == ret assert messages.REFRESH_CONFIG_SUCCESS in capsys.readouterr()[0] assert [mock.call()] == m_process_config.call_args_list + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("uaclient.apt_news.update_apt_news") @mock.patch("uaclient.cli.refresh_motd") @mock.patch("uaclient.cli.update_motd_messages") @@ -246,13 +277,16 @@ m_update_motd, m_refresh_motd, m_update_apt_news, + _m_check_lock_info, capsys, FakeConfig, ): """On success from process_config root user gets success message.""" cfg = FakeConfig.for_attached_machine() - ret = action_refresh(mock.MagicMock(target=None), cfg=cfg) + with mock.patch.object(lock, "lock_data_file"): + ret = action_refresh(mock.MagicMock(target=None), cfg=cfg) + out, err = capsys.readouterr() assert 0 == ret diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_security_status.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_security_status.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_security_status.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_security_status.py 2024-04-23 13:37:02.000000000 +0000 @@ -54,7 +54,7 @@ @mock.patch(M_PATH + "security_status.security_status_dict") @mock.patch(M_PATH + "contract.get_available_resources") class TestActionSecurityStatus: - @mock.patch(M_PATH + "setup_logging") + @mock.patch(M_PATH + "log.setup_cli_logging") def test_security_status_help( self, _m_setup_logging, @@ -121,7 +121,7 @@ assert m_security_status.call_args_list == [mock.call(cfg)] assert m_security_status.call_count == 1 - @mock.patch(M_PATH + "setup_logging") + @mock.patch(M_PATH + "log.setup_cli_logging") def test_error_on_wrong_format( self, _m_setup_logging, diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_status.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_status.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_status.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_status.py 2024-04-23 13:37:02.000000000 +0000 @@ -10,11 +10,12 @@ import mock import pytest -from uaclient import exceptions, messages, status, util +from uaclient import exceptions, lock, messages, status, util from uaclient.cli import action_status, get_parser, main, status_parser from uaclient.conftest import FakeNotice from uaclient.event_logger import EventLoggerMode from uaclient.files.notices import Notice, NoticesManager +from uaclient.files.user_config_file import UserConfigData from uaclient.yaml import safe_load M_PATH = "uaclient.cli." @@ -341,11 +342,17 @@ "uaclient.status.get_contract_information", return_value=RESPONSE_CONTRACT_INFO, ) +@mock.patch( + "uaclient.files.user_config_file.UserConfigFileObject.public_config", + new_callable=mock.PropertyMock, + return_value=UserConfigData(), +) class TestActionStatus: - @mock.patch(M_PATH + "setup_logging") + @mock.patch(M_PATH + "log.setup_cli_logging") def test_status_help( self, _m_setup_logging, + _m_public_config, _m_get_contract_information, _m_get_available_resources, _m_should_reboot, @@ -372,8 +379,8 @@ ([], ""), ( [ - [FakeNotice.a, "adesc"], - [FakeNotice.b, "bdesc"], + [FakeNotice.reboot_required, "adesc"], + [FakeNotice.enable_reboot_required, "bdesc"], ], "\nNOTICES\nadesc\nbdesc\n", ), @@ -389,10 +396,13 @@ ), ), ) + @mock.patch("uaclient.files.state_files.status_cache_file.write") @mock.patch("uaclient.status.format_expires", return_value="formatteddate") def test_attached( self, _m_format_expires, + _m_status_cache_file, + _m_public_config, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, @@ -449,8 +459,11 @@ (False), ), ) + @mock.patch("uaclient.files.state_files.status_cache_file.write") def test_unattached( self, + _m_status_cache_file, + _m_public_config, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, @@ -478,6 +491,7 @@ ) def test_simulated( self, + _m_public_config, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, @@ -497,6 +511,8 @@ ) assert expected == capsys.readouterr()[0] + @mock.patch("uaclient.files.state_files.status_cache_file.write") + @mock.patch("uaclient.lock.check_lock_info") @mock.patch("uaclient.version.get_version", return_value="test_version") @mock.patch("uaclient.system.subp") @mock.patch(M_PATH + "time.sleep") @@ -505,6 +521,9 @@ m_sleep, _m_subp, _m_get_version, + m_check_lock_info, + _m_status_cache_file, + _m_public_config, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, @@ -516,19 +535,20 @@ """Check that --wait will will block and poll until lock released.""" cfg = FakeConfig() mock_notice = NoticesManager() - lock_file = cfg.data_path("lock") - cfg.write_cache("lock", "123:pro auto-attach") + m_check_lock_info.return_value = (123, "pro disable") def fake_sleep(seconds): if m_sleep.call_count == 3: - os.unlink(lock_file) + m_check_lock_info.return_value = (-1, "") mock_notice.remove(Notice.OPERATION_IN_PROGRESS) m_sleep.side_effect = fake_sleep - assert 0 == action_status( - mock.MagicMock(all=False, simulate_with_token=None), cfg=cfg - ) + with mock.patch.object(lock, "lock_data_file"): + assert 0 == action_status( + mock.MagicMock(all=False, simulate_with_token=None), cfg=cfg + ) + assert [mock.call(1)] * 3 == m_sleep.call_args_list assert "...\n" + UNATTACHED_STATUS == capsys.readouterr()[0] @@ -555,8 +575,11 @@ }, ), ) + @mock.patch("uaclient.files.state_files.status_cache_file.write") def test_unattached_formats( self, + _m_status_cache_file, + _m_public_config, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, @@ -665,8 +688,11 @@ ), ) @pytest.mark.parametrize("use_all", (True, False)) + @mock.patch("uaclient.files.state_files.status_cache_file.write") def test_attached_formats( self, + _m_status_cache_file, + _m_public_config, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, @@ -800,6 +826,7 @@ @pytest.mark.parametrize("use_all", (True, False)) def test_simulated_formats( self, + _m_public_config, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, @@ -951,6 +978,7 @@ def test_error_on_connectivity_errors( self, + _m_public_config, _m_get_contract_information, m_get_avail_resources, _m_should_reboot, @@ -975,10 +1003,13 @@ "encoding,expected_dash", (("utf-8", "\u2014"), ("UTF-8", "\u2014"), ("ascii", "-")), ) + @mock.patch("uaclient.files.state_files.status_cache_file.write") @mock.patch("uaclient.status.format_expires", return_value="formatteddate") def test_unicode_dash_replacement_when_unprintable( self, _m_format_expires, + _m_status_cache_file, + _m_public_config, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, @@ -1040,6 +1071,7 @@ ) def test_errors_are_raised_appropriately( self, + _m_public_config, m_get_contract_information, _m_get_avail_resources, _m_should_reboot, @@ -1101,6 +1133,7 @@ ) def test_errors_for_token_dates( self, + _m_public_config, m_get_contract_information, _m_get_avail_resources, _m_should_reboot, diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_util.py ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_util.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/cli/tests/test_cli_util.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/cli/tests/test_cli_util.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,149 @@ +import mock +import pytest + +from uaclient import lock +from uaclient.cli.cli_util import ( + assert_attached, + assert_lock_file, + assert_not_attached, + assert_root, +) +from uaclient.exceptions import ( + AlreadyAttachedError, + NonRootUserError, + UnattachedError, +) +from uaclient.files.notices import Notice + +M_PATH_UACONFIG = "uaclient.config.UAConfig." + + +class TestAssertLockFile: + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) + @mock.patch("os.getpid", return_value=123) + @mock.patch("uaclient.files.notices.NoticesManager.add") + def test_assert_root_creates_lock_and_notice( + self, + m_add_notice, + _m_getpid, + _m_check_lock_info, + FakeConfig, + ): + arg, kwarg = mock.sentinel.arg, mock.sentinel.kwarg + + @assert_lock_file("some operation") + def test_function(args, cfg): + assert arg == mock.sentinel.arg + assert kwarg == mock.sentinel.kwarg + + return mock.sentinel.success + + with mock.patch.object(lock, "lock_data_file") as m_lock_file: + ret = test_function(arg, cfg=mock.MagicMock()) + + assert mock.sentinel.success == ret + lock_msg = "Operation in progress: some operation" + assert [ + mock.call(Notice.OPERATION_IN_PROGRESS, lock_msg) + ] == m_add_notice.call_args_list + assert [ + mock.call( + lock.LockData(lock_pid="123", lock_holder="some operation") + ) + ] == m_lock_file.write.call_args_list + assert 1 == m_lock_file.delete.call_count + + +class TestAssertRoot: + def test_assert_root_when_root(self): + # autouse mock for we_are_currently_root defaults it to True + arg, kwarg = mock.sentinel.arg, mock.sentinel.kwarg + + @assert_root + def test_function(arg, *, kwarg): + assert arg == mock.sentinel.arg + assert kwarg == mock.sentinel.kwarg + + return mock.sentinel.success + + ret = test_function(arg, kwarg=kwarg) + + assert mock.sentinel.success == ret + + def test_assert_root_when_not_root(self): + @assert_root + def test_function(): + pass + + with mock.patch( + "uaclient.cli.util.we_are_currently_root", return_value=False + ): + with pytest.raises(NonRootUserError): + test_function() + + +# Test multiple uids, to be sure that the root checking is absent +@pytest.mark.parametrize("root", [True, False]) +class TestAssertAttached: + def test_assert_attached_when_attached(self, capsys, root, FakeConfig): + @assert_attached() + def test_function(args, cfg): + return mock.sentinel.success + + cfg = FakeConfig.for_attached_machine() + + with mock.patch( + "uaclient.cli.util.we_are_currently_root", return_value=root + ): + ret = test_function(mock.Mock(), cfg) + + assert mock.sentinel.success == ret + + out, _err = capsys.readouterr() + assert "" == out.strip() + + def test_assert_attached_when_unattached(self, root, FakeConfig): + @assert_attached() + def test_function(args, cfg): + pass + + cfg = FakeConfig() + + with mock.patch( + "uaclient.cli.util.we_are_currently_root", return_value=root + ): + with pytest.raises(UnattachedError): + test_function(mock.Mock(), cfg) + + +@pytest.mark.parametrize("root", [True, False]) +class TestAssertNotAttached: + def test_when_attached(self, root, FakeConfig): + @assert_not_attached + def test_function(args, cfg): + pass + + cfg = FakeConfig.for_attached_machine() + + with mock.patch( + "uaclient.cli.util.we_are_currently_root", return_value=root + ): + with pytest.raises(AlreadyAttachedError): + test_function(mock.Mock(), cfg) + + def test_when_not_attached(self, capsys, root, FakeConfig): + @assert_not_attached + def test_function(args, cfg): + return mock.sentinel.success + + cfg = FakeConfig() + + with mock.patch( + "uaclient.cli.util.we_are_currently_root", return_value=root + ): + ret = test_function(mock.Mock(), cfg) + + assert mock.sentinel.success == ret + + out, _err = capsys.readouterr() + assert "" == out.strip() diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/clouds/aws.py ubuntu-advantage-tools-32~16.04/uaclient/clouds/aws.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/clouds/aws.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/clouds/aws.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,7 +1,7 @@ import logging from typing import Any, Dict -from uaclient import exceptions, http, system, util +from uaclient import exceptions, http, secret_manager, system, util from uaclient.clouds import AutoAttachCloudInstance IMDS_IPV4_ADDRESS = "169.254.169.254" @@ -43,7 +43,9 @@ @property # type: ignore @util.retry(exceptions.CloudMetadataError, retry_sleeps=[0.5, 1, 1]) def identity_doc(self) -> Dict[str, Any]: - return {"pkcs7": self._get_imds_url_response()} + imds_url_response = self._get_imds_url_response() + secret_manager.secrets.add_secret(imds_url_response) + return {"pkcs7": imds_url_response} def _request_imds_v2_token_headers(self): for address in IMDS_IP_ADDRESS: @@ -79,6 +81,7 @@ ) if response.code == 200: self._api_token = response.body + secret_manager.secrets.add_secret(self._api_token) return {AWS_TOKEN_PUT_HEADER: self._api_token} if response.code == 404: self._api_token = "IMDSv1" diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/clouds/azure.py ubuntu-advantage-tools-32~16.04/uaclient/clouds/azure.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/clouds/azure.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/clouds/azure.py 2024-04-23 13:37:02.000000000 +0000 @@ -2,7 +2,7 @@ import os from typing import Any, Dict -from uaclient import exceptions, http, system, util +from uaclient import exceptions, http, secret_manager, system, util from uaclient.clouds import AutoAttachCloudInstance LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) @@ -37,7 +37,9 @@ code=response.code, body=response.body ) if key == "pkcs7": - responses[key] = response.json_dict["signature"] + signature = response.json_dict["signature"] + responses[key] = signature + secret_manager.secrets.add_secret(signature) else: responses[key] = response.json_dict return responses diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/clouds/gcp.py ubuntu-advantage-tools-32~16.04/uaclient/clouds/gcp.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/clouds/gcp.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/clouds/gcp.py 2024-04-23 13:37:02.000000000 +0000 @@ -4,7 +4,7 @@ import os from typing import Any, Dict, List, Optional # noqa: F401 -from uaclient import exceptions, http, system, util +from uaclient import exceptions, http, secret_manager, system, util from uaclient.clouds import AutoAttachCloudInstance LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) @@ -29,6 +29,7 @@ "bionic": "6022427724719891830", "focal": "599959289349842382", "jammy": "2592866803419978320", + "noble": "2176054482269786025", } @@ -47,6 +48,7 @@ TOKEN_URL, headers={"Metadata-Flavor": "Google"}, timeout=1 ) if response.code == 200: + secret_manager.secrets.add_secret(response.body) return {"identityToken": response.body} error_desc = response.json_dict.get("error_description") diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/config.py ubuntu-advantage-tools-32~16.04/uaclient/config.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/config.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/config.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,10 +1,8 @@ import copy -import json import logging import os -from collections import namedtuple from functools import lru_cache, wraps -from typing import Any, Dict, Optional, Tuple +from typing import Any, Dict, Optional from uaclient import ( apt, @@ -26,10 +24,8 @@ CONFIG_FIELD_ENVVAR_ALLOWLIST, DEFAULT_CONFIG_FILE, DEFAULT_DATA_DIR, - PRIVATE_SUBDIR, ) -from uaclient.files import notices, state_files -from uaclient.files.notices import Notice +from uaclient.files import user_config_file from uaclient.yaml import safe_load LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) @@ -69,21 +65,11 @@ "livepatch_url", ) -# A data path is a filename, an attribute ("private") indicating whether it -# should only be readable by root. -DataPath = namedtuple("DataPath", ("filename", "private")) event = event_logger.get_event_logger() class UAConfig: - data_paths = { - "instance-id": DataPath("instance-id", True), - "machine-access-cis": DataPath("machine-access-cis.json", True), - "lock": DataPath("lock", False), - "status-cache": DataPath("status.json", False), - } # type: Dict[str, DataPath] - ua_scoped_proxy_options = ("ua_apt_http_proxy", "ua_apt_https_proxy") global_scoped_proxy_options = ( "global_apt_http_proxy", @@ -97,7 +83,7 @@ def __init__( self, cfg: Optional[Dict[str, Any]] = None, - user_config: Optional[state_files.UserConfigData] = None, + user_config: Optional[user_config_file.UserConfigData] = None, series: Optional[str] = None, ) -> None: """""" @@ -113,19 +99,16 @@ self.user_config = user_config else: try: - self.user_config = ( - state_files.user_config_file.read() - or state_files.UserConfigData() - ) + self.user_config = user_config_file.user_config.read() except Exception as e: LOG.warning("Error loading user config", exc_info=e) LOG.warning("Using default config values") - self.user_config = state_files.UserConfigData() + self.user_config = user_config_file.UserConfigData() # support old ua_config values in uaclient.conf as user-config.json # value overrides if "ua_config" in self.cfg: - self.user_config = state_files.UserConfigData.from_dict( + self.user_config = user_config_file.UserConfigData.from_dict( {**self.user_config.to_dict(), **self.cfg["ua_config"]}, optional_type_errors_become_null=True, ) @@ -163,7 +146,7 @@ @http_proxy.setter def http_proxy(self, value: str): self.user_config.http_proxy = value - state_files.user_config_file.write(self.user_config) + user_config_file.user_config.write(self.user_config) @property def https_proxy(self) -> Optional[str]: @@ -172,7 +155,7 @@ @https_proxy.setter def https_proxy(self, value: str): self.user_config.https_proxy = value - state_files.user_config_file.write(self.user_config) + user_config_file.user_config.write(self.user_config) @property def ua_apt_https_proxy(self) -> Optional[str]: @@ -181,7 +164,7 @@ @ua_apt_https_proxy.setter def ua_apt_https_proxy(self, value: str): self.user_config.ua_apt_https_proxy = value - state_files.user_config_file.write(self.user_config) + user_config_file.user_config.write(self.user_config) @property def ua_apt_http_proxy(self) -> Optional[str]: @@ -190,7 +173,7 @@ @ua_apt_http_proxy.setter def ua_apt_http_proxy(self, value: str): self.user_config.ua_apt_http_proxy = value - state_files.user_config_file.write(self.user_config) + user_config_file.user_config.write(self.user_config) @property # type: ignore @lru_cache(maxsize=None) @@ -214,7 +197,7 @@ self.user_config.global_apt_http_proxy = value self.user_config.apt_http_proxy = None UAConfig.global_apt_http_proxy.fget.cache_clear() # type: ignore - state_files.user_config_file.write(self.user_config) + user_config_file.user_config.write(self.user_config) @property # type: ignore @lru_cache(maxsize=None) @@ -238,7 +221,7 @@ self.user_config.global_apt_https_proxy = value self.user_config.apt_https_proxy = None UAConfig.global_apt_https_proxy.fget.cache_clear() # type: ignore - state_files.user_config_file.write(self.user_config) + user_config_file.user_config.write(self.user_config) @property def update_messaging_timer(self) -> int: @@ -250,7 +233,7 @@ @update_messaging_timer.setter def update_messaging_timer(self, value: int): self.user_config.update_messaging_timer = value - state_files.user_config_file.write(self.user_config) + user_config_file.user_config.write(self.user_config) @property def metering_timer(self) -> int: @@ -262,7 +245,7 @@ @metering_timer.setter def metering_timer(self, value: int): self.user_config.metering_timer = value - state_files.user_config_file.write(self.user_config) + user_config_file.user_config.write(self.user_config) @property def poll_for_pro_license(self) -> bool: @@ -277,7 +260,7 @@ @poll_for_pro_license.setter def poll_for_pro_license(self, value: bool): self.user_config.poll_for_pro_license = value - state_files.user_config_file.write(self.user_config) + user_config_file.user_config.write(self.user_config) @property def polling_error_retry_delay(self) -> int: @@ -291,7 +274,7 @@ @polling_error_retry_delay.setter def polling_error_retry_delay(self, value: int): self.user_config.polling_error_retry_delay = value - state_files.user_config_file.write(self.user_config) + user_config_file.user_config.write(self.user_config) @property def apt_news(self) -> bool: @@ -303,7 +286,7 @@ @apt_news.setter def apt_news(self, value: bool): self.user_config.apt_news = value - state_files.user_config_file.write(self.user_config) + user_config_file.user_config.write(self.user_config) @property def apt_news_url(self) -> str: @@ -315,50 +298,7 @@ @apt_news_url.setter def apt_news_url(self, value: str): self.user_config.apt_news_url = value - state_files.user_config_file.write(self.user_config) - - def check_lock_info(self) -> Tuple[int, str]: - """Return lock info if config lock file is present the lock is active. - - If process claiming the lock is no longer present, remove the lock file - and log a warning. - - :param lock_path: Full path to the lock file. - - :return: A tuple (pid, string describing lock holder) - If no active lock, pid will be -1. - """ - lock_path = self.data_path("lock") - no_lock = (-1, "") - if not os.path.exists(lock_path): - return no_lock - lock_content = system.load_file(lock_path) - - try: - [lock_pid, lock_holder] = lock_content.split(":") - except ValueError: - raise exceptions.InvalidLockFile( - lock_file_path=os.path.join(self.data_dir, "lock") - ) - - try: - system.subp(["ps", lock_pid]) - return (int(lock_pid), lock_holder) - except exceptions.ProcessExecutionError: - if not util.we_are_currently_root(): - LOG.debug( - "Found stale lock file previously held by %s:%s", - lock_pid, - lock_holder, - ) - return (int(lock_pid), lock_holder) - LOG.warning( - "Removing stale lock file previously held by %s:%s", - lock_pid, - lock_holder, - ) - system.ensure_file_absent(lock_path) - return no_lock + user_config_file.user_config.write(self.user_config) @property def data_dir(self): @@ -396,80 +336,6 @@ """Return the machine-token if cached in the machine token response.""" return self.machine_token_file.machine_token - def data_path(self, key: Optional[str] = None) -> str: - """Return the file path in the data directory represented by the key""" - data_dir = self.data_dir - if not key: - return os.path.join(data_dir, PRIVATE_SUBDIR) - if key in self.data_paths: - data_path = self.data_paths[key] - if data_path.private: - return os.path.join( - data_dir, PRIVATE_SUBDIR, data_path.filename - ) - return os.path.join(data_dir, data_path.filename) - return os.path.join(data_dir, PRIVATE_SUBDIR, key) - - def cache_key_exists(self, key: str) -> bool: - cache_path = self.data_path(key) - return os.path.exists(cache_path) - - def delete_cache_key(self, key: str) -> None: - """Remove specific cache file.""" - if not key: - raise RuntimeError( - "Invalid or empty key provided to delete_cache_key" - ) - if key.startswith("machine-access"): - self._machine_token_file = None - elif key == "lock": - notices.remove(Notice.OPERATION_IN_PROGRESS) - cache_path = self.data_path(key) - system.ensure_file_absent(cache_path) - - def delete_cache(self): - """ - Remove configuration cached response files class attributes. - """ - for path_key in self.data_paths.keys(): - self.delete_cache_key(path_key) - - def read_cache(self, key: str, silent: bool = False) -> Optional[Any]: - cache_path = self.data_path(key) - try: - content = system.load_file(cache_path) - except Exception: - if not os.path.exists(cache_path) and not silent: - LOG.debug("File does not exist: %s", cache_path) - return None - try: - return json.loads(content, cls=util.DatetimeAwareJSONDecoder) - except ValueError: - return content - - def write_cache(self, key: str, content: Any) -> None: - filepath = self.data_path(key) - data_dir = os.path.dirname(filepath) - if not os.path.exists(data_dir): - os.makedirs(data_dir, exist_ok=True) - if os.path.basename(data_dir) == PRIVATE_SUBDIR: - os.chmod(data_dir, 0o700) - if key.startswith("machine-access"): - self._machine_token_file = None - elif key == "lock": - if ":" in content: - notices.add( - Notice.OPERATION_IN_PROGRESS, - operation=content.split(":")[1], - ) - if not isinstance(content, str): - content = json.dumps(content, cls=util.DatetimeAwareJSONEncoder) - mode = 0o600 - if key in self.data_paths: - if not self.data_paths[key].private: - mode = 0o644 - system.write_file(filepath, content, mode=mode) - def process_config(self): for prop in ( "update_messaging_timer", @@ -585,7 +451,7 @@ LOG.warning('legacy "ua_config" found in uaclient.conf') LOG.warning("Please do the following:") LOG.warning( - " 1. run `pro config set field=value` for each" + " 1. run `sudo pro config set field=value` for each" ' field/value pair present under "ua_config" in' " /etc/ubuntu-advantage/uaclient.conf" ) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/conftest.py ubuntu-advantage-tools-32~16.04/uaclient/conftest.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/conftest.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/conftest.py 2024-04-23 13:37:02.000000000 +0000 @@ -13,8 +13,9 @@ try: from uaclient import event_logger from uaclient.config import UAConfig + from uaclient.entitlements.entitlement_status import ApplicationStatus from uaclient.files.notices import NoticeFileDetails - from uaclient.files.state_files import UserConfigData + from uaclient.files.user_config_file import UserConfigData except ImportError: raise @@ -266,7 +267,6 @@ config = cls() config.machine_token_file._machine_token = machine_token - config.write_cache("status-cache", status_cache) return config def override_features(self, features_override): @@ -285,9 +285,15 @@ class FakeNotice(NoticeFileDetails, Enum): - a = NoticeFileDetails("01", "a", True, "notice_a") - a2 = NoticeFileDetails("03", "a2", True, "notice_a2") - b = NoticeFileDetails("02", "b2", False, "notice_b") + reboot_required = NoticeFileDetails( + "10", "reboot_required", False, "notice_a" + ) + reboot_script_failed = NoticeFileDetails( + "12", "reboot_script_failed", True, "notice_a2" + ) + enable_reboot_required = NoticeFileDetails( + "11", "enable_reboot_required", False, "notice_b" + ) @pytest.yield_fixture(autouse=True) @@ -303,3 +309,28 @@ temp_dir.strpath, ): yield + + +@pytest.fixture +def mock_entitlement(): + def factory_func( + *, + name="test", + title="test", + application_status=(ApplicationStatus.DISABLED, ""), + can_disable=(False, None), + disable=(False, None), + enable=(False, None) + ): + m_ent_cls = mock.MagicMock() + type(m_ent_cls).name = mock.PropertyMock(return_value=name) + m_ent_obj = m_ent_cls.return_value + type(m_ent_obj).title = mock.PropertyMock(return_value=title) + m_ent_obj.application_status.return_value = application_status + m_ent_obj.disable.return_value = disable + m_ent_obj.enable.return_value = enable + m_ent_obj.can_disable.return_value = can_disable + + return (m_ent_cls, m_ent_obj) + + return factory_func diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/contract.py ubuntu-advantage-tools-32~16.04/uaclient/contract.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/contract.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/contract.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,7 +1,7 @@ import copy -import enum import logging import socket +from collections import namedtuple from typing import Any, Dict, List, Optional, Tuple from uaclient import ( @@ -10,6 +10,7 @@ exceptions, http, messages, + secret_manager, system, util, version, @@ -17,13 +18,10 @@ from uaclient.api.u.pro.status.enabled_services.v1 import _enabled_services from uaclient.api.u.pro.status.is_attached.v1 import _is_attached from uaclient.config import UAConfig -from uaclient.defaults import ( - ATTACH_FAIL_DATE_FORMAT, - CONTRACT_EXPIRY_GRACE_PERIOD_DAYS, - CONTRACT_EXPIRY_PENDING_DAYS, -) -from uaclient.files.state_files import attachment_data_file +from uaclient.defaults import ATTACH_FAIL_DATE_FORMAT +from uaclient.files.state_files import attachment_data_file, machine_id_file from uaclient.http import serviceclient +from uaclient.log import get_user_or_root_log_file_path # Here we describe every endpoint from the ua-contracts # service that is used by this client implementation. @@ -59,13 +57,9 @@ LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) -@enum.unique -class ContractExpiryStatus(enum.Enum): - NONE = 0 - ACTIVE = 1 - ACTIVE_EXPIRED_SOON = 2 - EXPIRED_GRACE_PERIOD = 3 - EXPIRED = 4 +EnableByDefaultService = namedtuple( + "EnableByDefaultService", ["name", "variant"] +) class UAContractClient(serviceclient.UAServiceClient): @@ -106,8 +100,13 @@ code=response.code, body=response.body, ) - - return response.json_dict + response_json = response.json_dict + secret_manager.secrets.add_secret( + response_json.get("machineToken", "") + ) + for token in response_json.get("resourceTokens", []): + secret_manager.secrets.add_secret(token.get("token", "")) + return response_json def available_resources(self) -> Dict[str, Any]: """Requests list of entitlements available to this machine type.""" @@ -170,15 +169,17 @@ body=response.body, ) - self.cfg.write_cache("contract-token", response.json_dict) - return response.json_dict + response_json = response.json_dict + secret_manager.secrets.add_secret( + response_json.get("contractToken", "") + ) + return response_json def get_resource_machine_access( self, machine_token: str, resource: str, machine_id: Optional[str] = None, - save_file: bool = True, ) -> Dict[str, Any]: """Requests machine access context for a given resource @@ -187,7 +188,6 @@ @param resource: Entitlement name. @param machine_id: Optional unique system machine id. When absent, contents of /etc/machine-id will be used. - @save_file: If the machine access should be saved on the user machine @return: Dict of the JSON response containing entitlement accessInfo. """ @@ -207,11 +207,11 @@ ) if response.headers.get("expires"): response.json_dict["expires"] = response.headers["expires"] - if save_file: - self.cfg.write_cache( - "machine-access-{}".format(resource), response.json_dict - ) - return response.json_dict + + response_json = response.json_dict + for token in response_json.get("resourceTokens", []): + secret_manager.secrets.add_secret(token.get("token", "")) + return response_json def update_activity_token(self): """Report current activity token and enabled services. @@ -273,8 +273,11 @@ code=response.code, body=response.body, ) - - return response.json_dict + response_json = response.json_dict + secret_fields = ["token", "userCode", "contractToken"] + for field in secret_fields: + secret_manager.secrets.add_secret(response_json.get(field, "")) + return response_json def new_magic_attach_token(self) -> Dict[str, Any]: """Create a magic attach token for the user.""" @@ -293,8 +296,11 @@ code=response.code, body=response.body, ) - - return response.json_dict + response_json = response.json_dict + secret_fields = ["token", "userCode", "contractToken"] + for field in secret_fields: + secret_manager.secrets.add_secret(response_json.get(field, "")) + return response_json def revoke_magic_attach_token(self, magic_token: str): """Revoke a magic attach token for the user.""" @@ -428,9 +434,11 @@ for service in enabled_services if service.variant_enabled }, - "lastAttachment": attachment_data.attached_at.isoformat() - if attachment_data - else None, + "lastAttachment": ( + attachment_data.attached_at.isoformat() + if attachment_data + else None + ), } else: activity_info = {} @@ -492,7 +500,7 @@ from uaclient.entitlements import entitlements_enable_order delta_error = False - unexpected_error = False + unexpected_errors = [] # We need to sort our entitlements because some of them # depend on other service to be enable first. @@ -523,7 +531,7 @@ ) except Exception as e: LOG.exception(e) - unexpected_error = True + unexpected_errors.append(e) failed_services.append(name) LOG.exception( "Unexpected error processing contract delta for %s: %r", @@ -536,10 +544,17 @@ if service_enabled and deltas: event.service_processed(name) event.services_failed(failed_services) - if unexpected_error: + if len(unexpected_errors) > 0: raise exceptions.AttachFailureUnknownError( failed_services=[ - (name, messages.UNEXPECTED_ERROR) for name in failed_services + ( + name, + messages.UNEXPECTED_ERROR.format( + error_msg=str(exception), + log_path=get_user_or_root_log_file_path(), + ), + ) + for name, exception in zip(failed_services, unexpected_errors) ] ) elif delta_error: @@ -664,7 +679,7 @@ machine_id = resp.get("machineTokenInfo", {}).get( "machineId", system.get_machine_id(cfg) ) - cfg.write_cache("machine-id", machine_id) + machine_id_file.write(machine_id) process_entitlements_delta( cfg, @@ -751,9 +766,9 @@ series_overrides = entitlement.pop("series", {}).pop(series_name, {}) if series_overrides: - overrides[ - OVERRIDE_SELECTOR_WEIGHTS["series_overrides"] - ] = series_overrides + overrides[OVERRIDE_SELECTOR_WEIGHTS["series_overrides"]] = ( + series_overrides + ) general_overrides = copy.deepcopy(entitlement.get("overrides", [])) for override in general_overrides: @@ -815,28 +830,35 @@ orig_access["entitlement"][key] = value -def get_contract_expiry_status( - cfg: UAConfig, -) -> Tuple[ContractExpiryStatus, int]: - """Return a tuple [ContractExpiryStatus, num_days]""" - if not _is_attached(cfg).is_attached: - return ContractExpiryStatus.NONE, 0 - - grace_period = CONTRACT_EXPIRY_GRACE_PERIOD_DAYS - pending_expiry = CONTRACT_EXPIRY_PENDING_DAYS - remaining_days = cfg.machine_token_file.contract_remaining_days - - # if unknown assume the worst - if remaining_days is None: - LOG.warning( - "contract effectiveTo date is null - assuming it is expired" - ) - return ContractExpiryStatus.EXPIRED, -grace_period - - if 0 <= remaining_days <= pending_expiry: - return ContractExpiryStatus.ACTIVE_EXPIRED_SOON, remaining_days - elif -grace_period <= remaining_days < 0: - return ContractExpiryStatus.EXPIRED_GRACE_PERIOD, remaining_days - elif remaining_days < -grace_period: - return ContractExpiryStatus.EXPIRED, remaining_days - return ContractExpiryStatus.ACTIVE, remaining_days +def get_enabled_by_default_services( + cfg: UAConfig, entitlements: Dict[str, Any] +) -> List[EnableByDefaultService]: + from uaclient.entitlements import entitlement_factory + + enable_by_default_services = [] + + for ent_name, ent_value in entitlements.items(): + variant = ent_value.get("obligations", {}).get("use_selector", "") + + try: + ent_cls = entitlement_factory( + cfg=cfg, name=ent_name, variant=variant + ) + except exceptions.EntitlementNotFoundError: + continue + + ent = ent_cls(cfg) + obligations = ent_value.get("entitlement", {}).get("obligations", {}) + resourceToken = ent_value.get("resourceToken") + + if ent._should_enable_by_default(obligations, resourceToken): + can_enable, _ = ent.can_enable() + if can_enable: + enable_by_default_services.append( + EnableByDefaultService( + name=ent_name, + variant=variant, + ) + ) + + return enable_by_default_services diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/daemon/__init__.py ubuntu-advantage-tools-32~16.04/uaclient/daemon/__init__.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/daemon/__init__.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/daemon/__init__.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,14 +1,10 @@ import logging import os -import sys from subprocess import TimeoutExpired -from uaclient import exceptions -from uaclient import log as pro_log -from uaclient import system, util +from uaclient import exceptions, system, util from uaclient.config import UAConfig from uaclient.defaults import DEFAULT_DATA_DIR -from uaclient.log import JsonArrayFormatter LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) @@ -39,25 +35,3 @@ from uaclient.daemon import retry_auto_attach retry_auto_attach.cleanup(cfg) - - -def setup_logging(console_level, log_level, log_file, logger=None): - if logger is None: - logger = logging.getLogger("ubuntupro") - - logger.setLevel(log_level) - - logger.handlers = [] - logger.addFilter(pro_log.RedactionFilter()) - - console_handler = logging.StreamHandler(sys.stderr) - console_handler.setFormatter(logging.Formatter("%(message)s")) - console_handler.setLevel(console_level) - console_handler.set_name("upro-console") - logger.addHandler(console_handler) - - file_handler = logging.FileHandler(log_file) - file_handler.setFormatter(JsonArrayFormatter()) - file_handler.setLevel(log_level) - file_handler.set_name("upro-file") - logger.addHandler(file_handler) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/daemon/poll_for_pro_license.py ubuntu-advantage-tools-32~16.04/uaclient/daemon/poll_for_pro_license.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/daemon/poll_for_pro_license.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/daemon/poll_for_pro_license.py 2024-04-23 13:37:02.000000000 +0000 @@ -15,9 +15,7 @@ def attempt_auto_attach(cfg: UAConfig, cloud: AutoAttachCloudInstance): try: - with lock.RetryLock( - cfg=cfg, lock_holder="pro.daemon.attempt_auto_attach" - ): + with lock.RetryLock(lock_holder="pro.daemon.attempt_auto_attach"): actions.auto_attach(cfg, cloud) except Exception as e: LOG.error(e) @@ -25,26 +23,26 @@ LOG.info("creating flag file to trigger retries") system.create_file(retry_auto_attach.FLAG_FILE_PATH) return - LOG.debug("Successful auto attach") + LOG.info("Successful auto attach") def poll_for_pro_license(cfg: UAConfig): if util.is_config_value_true( config=cfg.cfg, path_to_value="features.disable_auto_attach" ): - LOG.debug("Configured to not auto attach, shutting down") + LOG.info("Configured to not auto attach, shutting down") return if _is_attached(cfg).is_attached: - LOG.debug("Already attached, shutting down") + LOG.info("Already attached, shutting down") return if not system.is_current_series_lts(): - LOG.debug("Not on LTS, shutting down") + LOG.info("Not on LTS, shutting down") return try: cloud = cloud_instance_factory() except exceptions.CloudFactoryError: - LOG.debug("Not on cloud, shutting down") + LOG.info("Not on cloud, shutting down") return is_supported_cloud = any( @@ -55,11 +53,11 @@ ) ) if not is_supported_cloud: - LOG.debug("Not on supported cloud platform, shutting down") + LOG.info("Not on supported cloud platform, shutting down") return if not cloud.should_poll_for_pro_license(): - LOG.debug("Not on supported instance, shutting down") + LOG.info("Not on supported instance, shutting down") return try: @@ -67,7 +65,7 @@ wait_for_change=False ) except exceptions.CancelProLicensePolling: - LOG.debug("Cancelling polling") + LOG.info("Cancelling polling") return except exceptions.DelayProLicensePolling: # Continue to polling loop anyway and handle error there if it occurs @@ -79,7 +77,7 @@ return if not cfg.poll_for_pro_license: - LOG.debug("Configured to not poll for pro license, shutting down") + LOG.info("Configured to not poll for pro license, shutting down") return while True: @@ -90,7 +88,7 @@ ) end = time.time() except exceptions.CancelProLicensePolling: - LOG.debug("Cancelling polling") + LOG.info("Cancelling polling") return except exceptions.DelayProLicensePolling: time.sleep(cfg.polling_error_retry_delay) @@ -98,13 +96,13 @@ else: if _is_attached(cfg).is_attached: # This could have changed during the long poll or sleep - LOG.debug("Already attached, shutting down") + LOG.info("Already attached, shutting down") return if pro_license_present: attempt_auto_attach(cfg, cloud) return if end - start < 10: - LOG.debug( + LOG.info( "wait_for_change returned quickly and no pro license" " present. Waiting %d seconds before polling again", cfg.polling_error_retry_delay, diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/daemon/retry_auto_attach.py ubuntu-advantage-tools-32~16.04/uaclient/daemon/retry_auto_attach.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/daemon/retry_auto_attach.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/daemon/retry_auto_attach.py 2024-04-23 13:37:02.000000000 +0000 @@ -59,7 +59,7 @@ return '"{}"'.format(e.msg) else: LOG.error("Unexpected exception", exc_info=e) - return str(e) or messages.RETRY_ERROR_DETAIL_UNKNOWN + return str(e) or messages.UNKNOWN_ERROR def cleanup(cfg: UAConfig): @@ -103,7 +103,7 @@ ) msg_reason = failure_reason if msg_reason is None: - msg_reason = messages.RETRY_ERROR_DETAIL_UNKNOWN + msg_reason = messages.UNKNOWN_ERROR try: next_attempt = next_attempt.astimezone() except Exception: @@ -119,7 +119,6 @@ ) try: with lock.RetryLock( - cfg=cfg, lock_holder="pro.daemon.retry_auto_attach.notice_updates", ): notices.add( @@ -169,7 +168,7 @@ ) msg_reason = failure_reason if msg_reason is None: - msg_reason = messages.RETRY_ERROR_DETAIL_UNKNOWN + msg_reason = messages.UNKNOWN_ERROR auto_attach_status_msg = ( messages.AUTO_ATTACH_RETRY_TOTAL_FAILURE_NOTICE.format( num_attempts=len(RETRY_INTERVALS) + 1, reason=msg_reason diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/daemon/tests/test_poll_for_pro_license.py ubuntu-advantage-tools-32~16.04/uaclient/daemon/tests/test_poll_for_pro_license.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/daemon/tests/test_poll_for_pro_license.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/daemon/tests/test_poll_for_pro_license.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,7 +1,7 @@ import mock import pytest -from uaclient import exceptions +from uaclient import exceptions, lock from uaclient.clouds.aws import UAAutoAttachAWSInstance from uaclient.clouds.gcp import UAAutoAttachGCPInstance from uaclient.daemon.poll_for_pro_license import ( @@ -24,39 +24,36 @@ return _time_mock_side_effect -@mock.patch(M_PATH + "LOG.debug") +@mock.patch(M_PATH + "LOG.info") @mock.patch(M_PATH + "actions.auto_attach") @mock.patch(M_PATH + "lock.RetryLock") class TestAttemptAutoAttach: - def test_success( - self, m_spin_lock, m_auto_attach, m_log_debug, FakeConfig - ): + def test_success(self, m_spin_lock, m_auto_attach, m_log_info, FakeConfig): cfg = FakeConfig() cloud = mock.MagicMock() - attempt_auto_attach(cfg, cloud) + with mock.patch.object(lock, "lock_data_file"): + attempt_auto_attach(cfg, cloud) assert [ - mock.call(cfg=cfg, lock_holder="pro.daemon.attempt_auto_attach") + mock.call(lock_holder="pro.daemon.attempt_auto_attach") ] == m_spin_lock.call_args_list assert [mock.call(cfg, cloud)] == m_auto_attach.call_args_list assert [ mock.call("Successful auto attach") - ] == m_log_debug.call_args_list + ] == m_log_info.call_args_list @mock.patch(M_PATH + "system.create_file") @mock.patch(M_PATH + "lock.clear_lock_file_if_present") @mock.patch(M_PATH + "LOG.error") - @mock.patch(M_PATH + "LOG.info") def test_exception( self, - m_log_info, m_log_error, m_clear_lock, m_create_file, m_spin_lock, m_auto_attach, - m_log_debug, + m_log_info, FakeConfig, ): err = Exception() @@ -67,7 +64,7 @@ attempt_auto_attach(cfg, cloud) assert [ - mock.call(cfg=cfg, lock_holder="pro.daemon.attempt_auto_attach") + mock.call(lock_holder="pro.daemon.attempt_auto_attach") ] == m_spin_lock.call_args_list assert [mock.call(cfg, cloud)] == m_auto_attach.call_args_list assert [mock.call(err)] == m_log_error.call_args_list @@ -80,7 +77,7 @@ ] == m_create_file.call_args_list -@mock.patch(M_PATH + "LOG.debug") +@mock.patch(M_PATH + "LOG.info") @mock.patch(M_PATH + "time.sleep") @mock.patch(M_PATH + "time.time") @mock.patch(M_PATH + "attempt_auto_attach") @@ -98,7 +95,7 @@ "should_poll," "is_pro_license_present," "cfg_poll_for_pro_licenses," - "expected_log_debug_calls," + "expected_log_info_calls," "expected_is_pro_license_present_calls," "expected_attempt_auto_attach_calls", [ @@ -242,7 +239,7 @@ m_attempt_auto_attach, m_time, m_sleep, - m_log_debug, + m_log_info, is_config_value_true, is_attached, is_current_series_lts, @@ -250,7 +247,7 @@ should_poll, is_pro_license_present, cfg_poll_for_pro_licenses, - expected_log_debug_calls, + expected_log_info_calls, expected_is_pro_license_present_calls, expected_attempt_auto_attach_calls, FakeConfig, @@ -269,7 +266,7 @@ poll_for_pro_license(cfg) - assert expected_log_debug_calls == m_log_debug.call_args_list + assert expected_log_info_calls == m_log_info.call_args_list assert ( expected_is_pro_license_present_calls == m_is_pro_license_present.call_args_list @@ -284,7 +281,7 @@ "time_side_effect," "expected_is_pro_license_present_calls," "expected_attempt_auto_attach_calls," - "expected_log_debug_calls," + "expected_log_info_calls," "expected_sleep_calls", [ ( @@ -402,12 +399,12 @@ m_attempt_auto_attach, m_time, m_sleep, - m_log_debug, + m_log_info, is_pro_license_present_side_effect, time_side_effect, expected_is_pro_license_present_calls, expected_attempt_auto_attach_calls, - expected_log_debug_calls, + expected_log_info_calls, expected_sleep_calls, FakeConfig, ): @@ -427,7 +424,7 @@ poll_for_pro_license(cfg) assert expected_sleep_calls == m_sleep.call_args_list - assert expected_log_debug_calls == m_log_debug.call_args_list + assert expected_log_info_calls == m_log_info.call_args_list assert ( expected_is_pro_license_present_calls == m_is_pro_license_present.call_args_list diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/defaults.py ubuntu-advantage-tools-32~16.04/uaclient/defaults.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/defaults.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/defaults.py 2024-04-23 13:37:02.000000000 +0000 @@ -5,35 +5,54 @@ any of our dependencies installed. """ -UAC_ETC_PATH = "/etc/ubuntu-advantage/" -UAC_RUN_PATH = "/run/ubuntu-advantage/" +import os + +# Base directories +UAC_ETC_PATH = "/etc/ubuntu-advantage" +UAC_RUN_PATH = "/run/ubuntu-advantage" DEFAULT_DATA_DIR = "/var/lib/ubuntu-advantage" +DEFAULT_LOG_DIR = "/var/log" + + +# Relative paths MACHINE_TOKEN_FILE = "machine-token.json" +CONFIG_FILE = "uaclient.conf" +USER_CONFIG_FILE = "user-config.json" +CANDIDATE_VERSION_FILE = "candidate-version" +DEFAULT_LOG_FILE_BASE_NAME = "ubuntu-advantage" PRIVATE_SUBDIR = "private" -DEFAULT_PRIVATE_MACHINE_TOKEN_PATH = ( - DEFAULT_DATA_DIR + "/" + PRIVATE_SUBDIR + "/" + MACHINE_TOKEN_FILE +MESSAGES_SUBDIR = "messages" +USER_CACHE_SUBDIR = "ubuntu-pro" +NOTICES_SUBDIR = "notices" +PRIVATE_ESM_CACHE_SUBDIR = "apt-esm" + +DEFAULT_PRIVATE_MACHINE_TOKEN_PATH = os.path.join( + DEFAULT_DATA_DIR, PRIVATE_SUBDIR, MACHINE_TOKEN_FILE +) +DEFAULT_PRIVATE_DATA_DIR = os.path.join(DEFAULT_DATA_DIR, PRIVATE_SUBDIR) +MESSAGES_DIR = os.path.join(DEFAULT_DATA_DIR, MESSAGES_SUBDIR) +DEFAULT_CONFIG_FILE = os.path.join(UAC_ETC_PATH, CONFIG_FILE) +CANDIDATE_CACHE_PATH = os.path.join(UAC_RUN_PATH, CANDIDATE_VERSION_FILE) +DEFAULT_USER_CONFIG_JSON_FILE = os.path.join( + DEFAULT_DATA_DIR, USER_CONFIG_FILE ) -DEFAULT_PRIVATE_DATA_DIR = DEFAULT_DATA_DIR + "/" + PRIVATE_SUBDIR -MESSAGES_SUBDIR = "/messages" -MESSAGES_DIR = DEFAULT_DATA_DIR + MESSAGES_SUBDIR -CANDIDATE_CACHE_PATH = UAC_RUN_PATH + "candidate-version" -DEFAULT_CONFIG_FILE = UAC_ETC_PATH + "uaclient.conf" -DEFAULT_USER_CONFIG_JSON_FILE = DEFAULT_DATA_DIR + "/user-config.json" -DEFAULT_UPGRADE_CONTRACT_FLAG_FILE = UAC_ETC_PATH + "request-update-contract" +DEFAULT_LOG_PREFIX = os.path.join(DEFAULT_LOG_DIR, DEFAULT_LOG_FILE_BASE_NAME) +ESM_APT_ROOTDIR = os.path.join(DEFAULT_DATA_DIR, PRIVATE_ESM_CACHE_SUBDIR) +NOTICES_PERMANENT_DIRECTORY = os.path.join(DEFAULT_DATA_DIR, NOTICES_SUBDIR) +NOTICES_TEMPORARY_DIRECTORY = os.path.join(UAC_RUN_PATH, NOTICES_SUBDIR) + + +# URLs BASE_CONTRACT_URL = "https://contracts.canonical.com" BASE_SECURITY_URL = "https://ubuntu.com/security" BASE_LIVEPATCH_URL = "https://livepatch.canonical.com" APT_NEWS_URL = "https://motd.ubuntu.com/aptnews.json" -CLOUD_BUILD_INFO = "/etc/cloud/build.info" -ESM_APT_ROOTDIR = DEFAULT_DATA_DIR + "/apt-esm/" PRINT_WRAP_WIDTH = 80 CONTRACT_EXPIRY_GRACE_PERIOD_DAYS = 14 CONTRACT_EXPIRY_PENDING_DAYS = 20 ATTACH_FAIL_DATE_FORMAT = "%B %d, %Y" -DEFAULT_LOG_DIR = "/var/log" -DEFAULT_LOG_FILE_BASE_NAME = "ubuntu-advantage" -DEFAULT_LOG_PREFIX = DEFAULT_LOG_DIR + "/" + DEFAULT_LOG_FILE_BASE_NAME + DEFAULT_LOG_FORMAT = ( "%(asctime)s - %(filename)s:(%(lineno)d) [%(levelname)s]: %(message)s" ) @@ -43,7 +62,7 @@ "security_url": BASE_SECURITY_URL, "data_dir": DEFAULT_DATA_DIR, "log_level": "debug", - "log_file": "/var/log/ubuntu-advantage.log", + "log_file": "{}.log".format(DEFAULT_LOG_PREFIX), } CONFIG_FIELD_ENVVAR_ALLOWLIST = [ @@ -55,13 +74,12 @@ ROOT_READABLE_MODE = 0o600 WORLD_READABLE_MODE = 0o644 -NOTICES_PERMANENT_DIRECTORY = DEFAULT_DATA_DIR + "/notices/" -NOTICES_TEMPORARY_DIRECTORY = UAC_RUN_PATH + "notices/" -USER_CACHE_SUBDIR = "ubuntu-pro" +CLOUD_BUILD_INFO = "/etc/cloud/build.info" SSL_CERTS_PATH = "/etc/ssl/certs/ca-certificates.crt" # used by apport, collect-logs, and tests APPARMOR_PROFILES = [ "/etc/apparmor.d/ubuntu_pro_apt_news", + "/etc/apparmor.d/ubuntu_pro_esm_cache", ] diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/__init__.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/__init__.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/__init__.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/__init__.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,6 +1,7 @@ import enum import textwrap -from typing import Dict, List, Type # noqa: F401 +from collections import defaultdict +from typing import Dict, List, NamedTuple, Optional, Tuple, Type # noqa: F401 from uaclient import exceptions from uaclient.config import UAConfig @@ -9,10 +10,12 @@ from uaclient.entitlements.base import UAEntitlement # noqa: F401 from uaclient.entitlements.cc import CommonCriteriaEntitlement from uaclient.entitlements.cis import CISEntitlement +from uaclient.entitlements.entitlement_status import ApplicabilityStatus from uaclient.entitlements.esm import ESMAppsEntitlement, ESMInfraEntitlement from uaclient.entitlements.landscape import LandscapeEntitlement from uaclient.entitlements.livepatch import LivepatchEntitlement from uaclient.entitlements.realtime import RealtimeKernelEntitlement +from uaclient.entitlements.repo import RepoEntitlement from uaclient.entitlements.ros import ROSEntitlement, ROSUpdatesEntitlement from uaclient.exceptions import EntitlementNotFoundError from uaclient.util import is_config_value_true @@ -142,9 +145,9 @@ return if sort_order == SortOrder.REQUIRED_SERVICES: - cls_list = ent_cls(cfg).required_services + cls_list = [e.entitlement for e in ent_cls(cfg).required_services] else: - cls_list = ent_cls(cfg).dependent_services + cls_list = list(ent_cls(cfg).dependent_services) for cls_dependency in cls_list: if ent_cls.name not in visited: @@ -217,3 +220,64 @@ invalid_service=", ".join(entitlements_not_found), service_msg=service_msg, ) + + +# This function is just here to simplify our unittests, as +# mocking isinstance directly doesn't work here +def _is_repo_entitlement(ent): + return isinstance(ent, RepoEntitlement) + + +def check_entitlement_apt_directives_are_unique( + cfg: UAConfig, +) -> bool: + entitlement_directives = defaultdict(list) + + for ent_name in valid_services(cfg): + ent = entitlement_factory(cfg, ent_name)(cfg) + + if not _is_repo_entitlement(ent): + continue + + applicability_status, _ = ent.applicability_status() + + if applicability_status == ApplicabilityStatus.APPLICABLE: + apt_url = ent.apt_url + apt_suites = ent.apt_suites or () + + for suite in apt_suites: + entitlement_directive = ent.repo_policy_check_tmpl.format( + apt_url, suite + ) + entitlement_directives[entitlement_directive].append( + { + "from": ent_name, + "apt_url": apt_url, + "suite": suite, + } + ) + + for def_path, ent_directive in entitlement_directives.items(): + if len(ent_directive) > 1: + apt_url = ent_directive[0]["apt_url"] + suite = ent_directive[0]["suite"] + + raise exceptions.EntitlementsAPTDirectivesAreNotUnique( + url=cfg.contract_url, + names=", ".join( + sorted(ent["from"] for ent in ent_directive) + ), + apt_url=apt_url, + suite=suite, + ) + + return True + + +def get_title(cfg: UAConfig, ent_name: str, variant=""): + try: + return entitlement_factory(cfg, ent_name, variant=variant)( + cfg, called_name=ent_name + ).title + except exceptions.UbuntuProError: + return ent_name diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/anbox.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/anbox.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/anbox.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/anbox.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional, Tuple -from uaclient import contract, event_logger, messages, system +from uaclient import api, contract, event_logger, messages, system from uaclient.entitlements.entitlement_status import ( CanEnableFailure, CanEnableFailureReason, @@ -51,8 +51,8 @@ return True, None - def _perform_enable(self, silent: bool = False) -> bool: - ret = super()._perform_enable(silent=silent) + def _perform_enable(self, progress: api.ProgressWrapper) -> bool: + ret = super()._perform_enable(progress) if not ret: return ret @@ -63,7 +63,7 @@ machine_token = self.cfg.machine_token["machineToken"] client = contract.UAContractClient(self.cfg) anbox_images_machine_access = client.get_resource_machine_access( - machine_token, "anbox-images", save_file=False + machine_token, "anbox-images" ) anbox_cloud_data = AnboxCloudData( @@ -81,8 +81,8 @@ return True - def _perform_disable(self, silent=False): - super()._perform_disable(silent=silent) + def _perform_disable(self, progress: api.ProgressWrapper): + super()._perform_disable(progress) anbox_cloud_credentials_file.delete() return True diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/base.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/base.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/base.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/base.py 2024-04-23 13:37:02.000000000 +0000 @@ -6,6 +6,7 @@ from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union from uaclient import ( + api, apt, config, contract, @@ -28,6 +29,7 @@ ContractStatus, UserFacingStatus, ) +from uaclient.files.state_files import status_cache_file from uaclient.types import MessagingOperationsDict, StaticAffordance from uaclient.util import is_config_value_true @@ -35,7 +37,7 @@ LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) -class IncompatibleService: +class EntitlementWithMessage: def __init__( self, entitlement: Type["UAEntitlement"], @@ -46,6 +48,9 @@ class UAEntitlement(metaclass=abc.ABCMeta): + # Required: short name of the entitlement + name = None # type: str + # Optional URL for top-level product service information help_doc_url = None # type: str @@ -65,10 +70,10 @@ help_text = "" # List of services that are incompatible with this service - _incompatible_services = () # type: Tuple[IncompatibleService, ...] + _incompatible_services = () # type: Tuple[EntitlementWithMessage, ...] # List of services that must be active before enabling this service - _required_services = () # type: Tuple[Type[UAEntitlement], ...] + _required_services = () # type: Tuple[EntitlementWithMessage, ...] # List of services that depend on this service _dependent_services = () # type: Tuple[Type[UAEntitlement], ...] @@ -82,12 +87,6 @@ is_variant = False @property - @abc.abstractmethod - def name(self) -> str: - """The lowercase name of this entitlement""" - pass - - @property def variant_name(self) -> str: """The lowercase name of this entitlement, in case it is a variant""" return "" @@ -163,7 +162,7 @@ return () @property - def incompatible_services(self) -> Tuple[IncompatibleService, ...]: + def incompatible_services(self) -> Tuple[EntitlementWithMessage, ...]: """ Return a list of packages that aren't compatible with the entitlement. When we are enabling the entitlement we can directly ask the user @@ -173,7 +172,7 @@ return self._incompatible_services @property - def required_services(self) -> Tuple[Type["UAEntitlement"], ...]: + def required_services(self) -> Tuple[EntitlementWithMessage, ...]: """ Return a list of packages that must be active before enabling this service. When we are enabling the entitlement we can directly ask @@ -336,6 +335,54 @@ return entitlement_cfg + @abc.abstractmethod + def enable_steps(self) -> int: + """ + The number of steps that are reported as progress while enabling + this specific entitlement that are not shared with other entitlements. + """ + pass + + @abc.abstractmethod + def disable_steps(self) -> int: + """ + The number of steps that are reported as progress while disabling + this specific entitlement that are not shared with other entitlements. + """ + pass + + def calculate_total_enable_steps(self) -> int: + total_steps = self.enable_steps() + required_snaps = ( + self.entitlement_cfg.get("entitlement", {}) + .get("directives", {}) + .get("requiredSnaps") + ) + if required_snaps is not None and len(required_snaps) > 0: + total_steps += 1 + required_packages = ( + self.entitlement_cfg.get("entitlement", {}) + .get("directives", {}) + .get("requiredPackages") + ) + if required_packages is not None and len(required_packages) > 0: + total_steps += 1 + for incompatible_service in self.blocking_incompatible_services(): + total_steps += incompatible_service.entitlement( + self.cfg + ).disable_steps() + for required_service in self.blocking_required_services(): + total_steps += required_service.entitlement( + self.cfg + ).enable_steps() + return total_steps + + def calculate_total_disable_steps(self) -> int: + total_steps = self.disable_steps() + for dependent_service in self.blocking_dependent_services(): + total_steps += dependent_service(self.cfg).disable_steps() + return total_steps + def can_enable(self) -> Tuple[bool, Optional[CanEnableFailure]]: """ Report whether or not enabling is possible for the entitlement. @@ -415,7 +462,7 @@ # support additional reason types in the future. def enable( self, - silent: bool = False, + progress: api.ProgressWrapper, ) -> Tuple[bool, Union[None, CanEnableFailure]]: """Enable specific entitlement. @@ -426,9 +473,9 @@ include other types of reasons in the future. """ - msg_ops = self.messaging.get("pre_can_enable", []) - if not util.handle_message_operations(msg_ops): - return False, None + progress.emit( + "message_operation", self.messaging.get("pre_can_enable") + ) can_enable, fail = self.can_enable() if not can_enable: @@ -437,7 +484,9 @@ return False, None elif fail.reason == CanEnableFailureReason.INCOMPATIBLE_SERVICE: # Try to disable those services before proceeding with enable - incompat_ret, error = self.handle_incompatible_services() + incompat_ret, error = self.handle_incompatible_services( + progress + ) if not incompat_ret: fail.message = error return False, fail @@ -446,7 +495,7 @@ == CanEnableFailureReason.INACTIVE_REQUIRED_SERVICES ): # Try to enable those services before proceeding with enable - req_ret, error = self._enable_required_services() + req_ret, error = self._enable_required_services(progress) if not req_ret: fail.message = error return False, fail @@ -454,31 +503,25 @@ # every other reason means we can't continue return False, fail - msg_ops = self.messaging.get("pre_enable", []) - if not util.handle_message_operations(msg_ops): - return False, None + progress.emit("message_operation", self.messaging.get("pre_enable")) # TODO: Move all logic from RepoEntitlement that # handles the additionalPackages and APT directives # to the base class. The additionalSnaps handling # is a step on that direction if not self.access_only: - if not self.handle_required_snaps(): + if not self.handle_required_snaps(progress): return False, None - if not self.handle_required_packages(): + if not self.handle_required_packages(progress): return False, None - ret = self._perform_enable(silent=silent) + ret = self._perform_enable(progress) if not ret: return False, None - msg_ops = self.messaging.get("post_enable", []) - if not util.handle_message_operations(msg_ops): - return False, None - return True, None - def handle_required_snaps(self) -> bool: + def handle_required_snaps(self, progress: api.ProgressWrapper) -> bool: """ "install snaps necessary to enable a service.""" required_snaps = ( self.entitlement_cfg.get("entitlement", {}) @@ -492,24 +535,28 @@ return True if not snap.is_snapd_installed(): - event.info(messages.INSTALLING_PACKAGES.format(packages="snapd")) + progress.emit( + "info", messages.INSTALLING_PACKAGES.format(packages="snapd") + ) snap.install_snapd() if not snap.is_snapd_installed_as_a_snap(): - event.info( - messages.INSTALLING_PACKAGES.format(packages="snapd snap") + progress.emit( + "info", + messages.INSTALLING_PACKAGES.format(packages="snapd snap"), ) try: snap.install_snap("snapd") except exceptions.ProcessExecutionError as e: LOG.warning("Failed to install snapd as a snap", exc_info=e) - event.info( + progress.emit( + "info", messages.EXECUTING_COMMAND_FAILED.format( command="snap install snapd" - ) + ), ) - snap.run_snapd_wait_cmd() + snap.run_snapd_wait_cmd(progress) try: snap.refresh_snap("snapd") @@ -534,7 +581,7 @@ ) if required_snaps: - event.info(messages.INSTALLING_REQUIRED_SNAPS) + progress.progress(messages.INSTALLING_REQUIRED_SNAPS) for snap_pkg in sorted(required_snaps, key=lambda x: x.get("name")): # The name field should always be delivered by the contract side @@ -547,10 +594,11 @@ ) channel = snap_pkg.get("channel") - event.info( + progress.emit( + "info", messages.INSTALLING_REQUIRED_SNAP_PACKAGE.format( snap=snap_name - ) + ), ) snap.install_snap( snap_name, @@ -580,7 +628,7 @@ [required in installed_packages for required in package_names] ) - def handle_required_packages(self) -> bool: + def handle_required_packages(self, progress: api.ProgressWrapper) -> bool: """install packages necessary to enable a service.""" required_packages = ( self.entitlement_cfg.get("entitlement", {}) @@ -593,11 +641,11 @@ if not required_packages: return True - self._update_sources_list() + self._update_sources_list(progress) package_names = [package["name"] for package in required_packages] LOG.debug("Installing packages %r", package_names) - event.info( + progress.progress( messages.INSTALLING_PACKAGES.format( packages=" ".join(package_names) ) @@ -644,7 +692,7 @@ return True @abc.abstractmethod - def _perform_enable(self, silent: bool = False) -> bool: + def _perform_enable(self, progress: api.ProgressWrapper) -> bool: """ Enable specific entitlement. This should be implemented by subclasses. This method does the actual enablement, and does not check can_enable @@ -654,39 +702,7 @@ """ pass - def detect_dependent_services(self) -> bool: - """ - Check for depedent services. - - :return: - True if there are dependent services enabled - False if there are no dependent services enabled - """ - for dependent_service_cls in self.dependent_services: - ent_status, _ = dependent_service_cls( - self.cfg - ).application_status() - if ent_status == ApplicationStatus.ENABLED: - return True - - return False - - def check_required_services_active(self): - """ - Check if all required services are active - - :return: - True if all required services are active - False is at least one of the required services is disabled - """ - for required_service_cls in self.required_services: - ent_status, _ = required_service_cls(self.cfg).application_status() - if ent_status != ApplicationStatus.ENABLED: - return False - - return True - - def blocking_incompatible_services(self) -> List[IncompatibleService]: + def blocking_incompatible_services(self) -> List[EntitlementWithMessage]: """ :return: List of incompatible services that are enabled """ @@ -713,6 +729,7 @@ def handle_incompatible_services( self, + progress: api.ProgressWrapper, ) -> Tuple[bool, Optional[messages.NamedMessage]]: """ Prompt user when incompatible services are found during enable. @@ -735,12 +752,7 @@ for service in self.blocking_incompatible_services(): ent = service.entitlement(self.cfg, assume_yes=True) - user_msg = messages.INCOMPATIBLE_SERVICE.format( - service_being_enabled=self.title, - incompatible_service=ent.title, - ) - - e_msg = messages.INCOMPATIBLE_SERVICE_STOPS_ENABLE.format( + e_msg = messages.E_INCOMPATIBLE_SERVICE_STOPS_ENABLE.format( service_being_enabled=self.title, incompatible_service=ent.title, ) @@ -748,25 +760,46 @@ if cfg_block_disable_on_enable: return False, e_msg - if not util.prompt_for_confirmation( - msg=user_msg, assume_yes=self.assume_yes - ): - return False, e_msg - - event.info( + progress.emit( + "info", messages.DISABLING_INCOMPATIBLE_SERVICE.format( service=ent.title - ) + ), ) - ret = ent.disable(silent=True) + ret, fail = ent.disable(progress) if not ret: - return ret, None + return (ret, fail.message if fail else None) return True, None + def check_required_services_active(self): + """ + Check if all required services are active + + :return: + True if all required services are active + False is at least one of the required services is disabled + """ + return len(self.blocking_required_services()) == 0 + + def blocking_required_services(self): + """ + :return: List of required services that are disabled + """ + ret = [] + for service in self.required_services: + ent_status, _ = service.entitlement(self.cfg).application_status() + if ent_status not in ( + ApplicationStatus.ENABLED, + ApplicationStatus.WARNING, + ): + ret.append(service) + return ret + def _enable_required_services( self, + progress: api.ProgressWrapper, ) -> Tuple[bool, Optional[messages.NamedMessage]]: """ Prompt user when required services are found during enable. @@ -775,44 +808,22 @@ that must be enabled first. In that situation, we can ask the user if the required service should be enabled before proceeding. """ - for required_service_cls in self.required_services: - ent = required_service_cls(self.cfg, allow_beta=True) - - is_service_disabled = ( - ent.application_status()[0] == ApplicationStatus.DISABLED + for required_service in self.blocking_required_services(): + ent = required_service.entitlement(self.cfg, allow_beta=True) + progress.emit( + "info", + messages.ENABLING_REQUIRED_SERVICE.format(service=ent.title), ) + ret, fail = ent.enable(progress) + if not ret: + error_msg = "" + if fail and fail.message and fail.message.msg: + error_msg = "\n" + fail.message.msg - if is_service_disabled: - user_msg = messages.REQUIRED_SERVICE.format( - service_being_enabled=self.title, - required_service=ent.title, - ) - - e_msg = messages.REQUIRED_SERVICE_STOPS_ENABLE.format( - service_being_enabled=self.title, - required_service=ent.title, + msg = messages.ERROR_ENABLING_REQUIRED_SERVICE.format( + error=error_msg, service=ent.title ) - - if not util.prompt_for_confirmation( - msg=user_msg, assume_yes=self.assume_yes - ): - return False, e_msg - - event.info( - messages.ENABLING_REQUIRED_SERVICE.format( - service=ent.title - ) - ) - ret, fail = ent.enable(silent=True) - if not ret: - error_msg = "" - if fail and fail.message and fail.message.msg: - error_msg = "\n" + fail.message.msg - - msg = messages.ERROR_ENABLING_REQUIRED_SERVICE.format( - error=error_msg, service=ent.title - ) - return ret, msg + return ret, msg return True, None @@ -880,7 +891,7 @@ return True, None def disable( - self, silent: bool = False + self, progress: api.ProgressWrapper ) -> Tuple[bool, Optional[CanDisableFailure]]: """Disable specific entitlement @@ -892,9 +903,7 @@ populated CanDisableFailure reason. This may expand to include other types of reasons in the future. """ - msg_ops = self.messaging.get("pre_disable", []) - if not util.handle_message_operations(msg_ops): - return False, None + progress.emit("message_operation", self.messaging.get("pre_disable")) can_disable, fail = self.can_disable() if not can_disable: @@ -905,7 +914,7 @@ fail.reason == CanDisableFailureReason.ACTIVE_DEPENDENT_SERVICES ): - ret, msg = self._disable_dependent_services(silent=silent) + ret, msg = self._disable_dependent_services(progress) if not ret: fail.message = msg return False, fail @@ -913,23 +922,18 @@ # every other reason means we can't continue return False, fail - if not self._perform_disable(silent=silent): + if not self._perform_disable(progress): return False, None if not self.handle_removing_required_packages(): return False, None - msg_ops = self.messaging.get("post_disable", []) - if not util.handle_message_operations(msg_ops): - return False, None + progress.emit("message_operation", self.messaging.get("post_disable")) - self._check_for_reboot_msg( - operation="disable operation", silent=silent - ) return True, None @abc.abstractmethod - def _perform_disable(self, silent: bool = False) -> bool: + def _perform_disable(self, progress: api.ProgressWrapper) -> bool: """ Disable specific entitlement. This should be implemented by subclasses. This method does the actual disable, and does not check can_disable @@ -941,8 +945,33 @@ """ pass + def detect_dependent_services(self) -> bool: + """ + Check for depedent services. + + :return: + True if there are dependent services enabled + False if there are no dependent services enabled + """ + return len(self.blocking_dependent_services()) > 0 + + def blocking_dependent_services(self) -> List[Type["UAEntitlement"]]: + """ + Return list of depedent services that must be disabled + before disabling this service. + """ + blocking = [] + for dependent_service_cls in self.dependent_services: + ent_status, _ = dependent_service_cls( + self.cfg + ).application_status() + if ent_status == ApplicationStatus.ENABLED: + blocking.append(dependent_service_cls) + + return blocking + def _disable_dependent_services( - self, silent: bool + self, progress: api.ProgressWrapper ) -> Tuple[bool, Optional[messages.NamedMessage]]: """ Disable dependent services @@ -955,52 +984,32 @@ @param silent: Boolean set True to silence print/log of messages """ - for dependent_service_cls in self.dependent_services: + for dependent_service_cls in self.blocking_dependent_services(): ent = dependent_service_cls(cfg=self.cfg, assume_yes=True) - is_service_enabled = ( - ent.application_status()[0] == ApplicationStatus.ENABLED + progress.emit( + "info", + messages.DISABLING_DEPENDENT_SERVICE.format( + required_service=ent.title + ), ) - if is_service_enabled: - user_msg = messages.DEPENDENT_SERVICE.format( - dependent_service=ent.title, - service_being_disabled=self.title, - ) + ret, fail = ent.disable(progress) + if not ret: + error_msg = "" + if fail and fail.message and fail.message.msg: + error_msg = "\n" + fail.message.msg - e_msg = messages.DEPENDENT_SERVICE_STOPS_DISABLE.format( - service_being_disabled=self.title, - dependent_service=ent.title, + msg = messages.FAILED_DISABLING_DEPENDENT_SERVICE.format( + error=error_msg, required_service=ent.title ) - - if not util.prompt_for_confirmation( - msg=user_msg, assume_yes=self.assume_yes - ): - return False, e_msg - - if not silent: - event.info( - messages.DISABLING_DEPENDENT_SERVICE.format( - required_service=ent.title - ) - ) - - ret, fail = ent.disable(silent=True) - if not ret: - error_msg = "" - if fail and fail.message and fail.message.msg: - error_msg = "\n" + fail.message.msg - - msg = messages.FAILED_DISABLING_DEPENDENT_SERVICE.format( - error=error_msg, required_service=ent.title - ) - return False, msg + return False, msg return True, None def _check_for_reboot(self) -> bool: - """Check if system needs to be rebooted.""" - return system.should_reboot() + """Check if system needs to be rebooted because of this service.""" + return False def _check_for_reboot_msg( self, operation: str, silent: bool = False @@ -1206,7 +1215,7 @@ def _check_application_status_on_cache(self) -> ApplicationStatus: """Check on the state of application on the status cache.""" - status_cache = self.cfg.read_cache("status-cache") + status_cache = status_cache_file.read() if status_cache is None: return ApplicationStatus.DISABLED @@ -1253,7 +1262,7 @@ delta_entitlement = deltas.get("entitlement", {}) delta_directives = delta_entitlement.get("directives", {}) - status_cache = self.cfg.read_cache("status-cache") + status_cache = status_cache_file.read() transition_to_unentitled = bool(delta_entitlement == util.DROPPED_KEY) if not transition_to_unentitled: @@ -1272,33 +1281,32 @@ application_status, _ = self.application_status() if application_status != ApplicationStatus.DISABLED: - if self.can_disable(): + can_disable, fail = self.can_disable() + if can_disable: LOG.info( - "Disabling %s after refresh transition to unentitled" + "Disabling %s after refresh transition to unentitled", + self.name, ) - self.disable() - msg = ( - "Due to contract refresh, " "'{}' is now disabled." - ).format(self.name) + self.disable(api.ProgressWrapper()) event.info( messages.DISABLE_DURING_CONTRACT_REFRESH.format( service=self.name ) ) else: + fail_msg = fail.message_value if fail else "" + LOG.warning( "Cannot disable %s after refresh transition to " - "unentitled" + "unentitled.\nReason: %s", + self.name, + fail_msg, ) event.info( messages.UNABLE_TO_DISABLE_DURING_CONTRACT_REFRESH.format( # noqa: E501 service=self.name ) ) - # Clean up former entitled machine-access- response cache - # file because uaclient doesn't access machine-access-* routes or - # responses on unentitled services. - self.cfg.delete_cache_key("machine-access-{}".format(self.name)) return True resourceToken = orig_access.get("resourceToken") @@ -1318,7 +1326,8 @@ msg = messages.ENABLE_BY_DEFAULT_TMPL.format(name=self.name) event.info(msg, file_type=sys.stderr) - self.enable() + self.enable(api.ProgressWrapper()) + event.info(messages.ENABLED_TMPL.format(title=self.title)) else: msg = messages.ENABLE_BY_DEFAULT_MANUAL_TMPL.format( name=self.name @@ -1328,9 +1337,11 @@ return False - def _update_sources_list(self): + def _update_sources_list(self, progress: api.ProgressWrapper): if self._is_sources_list_updated: return - event.info(messages.APT_UPDATING_LIST.format(name="standard Ubuntu")) + progress.emit( + "info", messages.APT_UPDATING_LIST.format(name="standard Ubuntu") + ) apt.update_sources_list(apt.get_system_sources_file()) self._is_sources_list_updated = True diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/entitlement_status.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/entitlement_status.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/entitlement_status.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/entitlement_status.py 2024-04-23 13:37:02.000000000 +0000 @@ -132,3 +132,7 @@ ) -> None: self.reason = reason self.message = message + + @property + def message_value(self) -> str: + return self.message.msg if self.message else "" diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/esm.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/esm.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/esm.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/esm.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,7 +1,7 @@ import os from typing import Tuple, Type, Union -from uaclient import messages, system +from uaclient import api, messages, system from uaclient.apt import APT_KEYS_DIR, DEB822_REPO_FILE_CONTENT, KEYRINGS_DIR from uaclient.defaults import ESM_APT_ROOTDIR from uaclient.entitlements import repo @@ -20,12 +20,12 @@ ROSUpdatesEntitlement, ) - return (ROSEntitlement, ROSUpdatesEntitlement) + return (ROSUpdatesEntitlement, ROSEntitlement) - def _perform_enable(self, silent: bool = False) -> bool: + def _perform_enable(self, progress: api.ProgressWrapper) -> bool: from uaclient.timer.update_messaging import update_motd_messages - enable_performed = super()._perform_enable(silent=silent) + enable_performed = super()._perform_enable(progress) if enable_performed: update_motd_messages(self.cfg) self.disable_local_esm_repo() @@ -99,11 +99,11 @@ repo_key_file = "ubuntu-pro-esm-apps.gpg" def disable( - self, silent=False + self, progress: api.ProgressWrapper ) -> Tuple[bool, Union[None, CanDisableFailure]]: from uaclient.timer.update_messaging import update_motd_messages - disable_performed, fail = super().disable(silent=silent) + disable_performed, fail = super().disable(progress) if disable_performed: update_motd_messages(self.cfg) if system.is_current_series_lts(): @@ -120,11 +120,11 @@ repo_key_file = "ubuntu-pro-esm-infra.gpg" def disable( - self, silent=False + self, progress: api.ProgressWrapper ) -> Tuple[bool, Union[None, CanDisableFailure]]: from uaclient.timer.update_messaging import update_motd_messages - disable_performed, fail = super().disable(silent=silent) + disable_performed, fail = super().disable(progress) if disable_performed: update_motd_messages(self.cfg) if system.is_current_series_active_esm(): diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/fips.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/fips.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/fips.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/fips.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,12 +1,13 @@ import logging import os +import re from itertools import groupby -from typing import List, Optional, Tuple # noqa: F401 +from typing import Callable, List, Optional, Tuple, Union # noqa: F401 -from uaclient import apt, event_logger, exceptions, messages, system, util +from uaclient import api, apt, event_logger, exceptions, messages, system, util from uaclient.clouds.identity import NoCloudTypeReason, get_cloud_type from uaclient.entitlements import repo -from uaclient.entitlements.base import IncompatibleService +from uaclient.entitlements.base import EntitlementWithMessage from uaclient.entitlements.entitlement_status import ApplicationStatus from uaclient.files import notices from uaclient.files.notices import Notice @@ -154,31 +155,83 @@ return FIPS_CONDITIONAL_PACKAGES.get(series, []) + def prompt_if_kernel_downgrade( + self, + assume_yes: bool = False, + ) -> bool: + """Check if installing a FIPS kernel will downgrade the kernel + and prompt for confirmation if it will. + """ + # Prior to installing packages, check if the kernel is being downgraded + # and if so verify that the user wants to continue + our_full_kernel_str = ( + system.get_kernel_info().proc_version_signature_version + ) + if our_full_kernel_str is None: + LOG.warning("Cannot gather kernel information") + return False + our_m = re.search( + r"(?P\d+\.\d+\.\d+)", our_full_kernel_str + ) + fips_kernel_version_str = apt.get_pkg_candidate_version("linux-fips") + if our_m is not None and fips_kernel_version_str is not None: + our_kernel_version_str = our_m.group("kernel_version") + LOG.debug( + "Kernel information: cur='%s' and fips='%s'", + our_full_kernel_str, + fips_kernel_version_str, + ) + if ( + apt.version_compare( + fips_kernel_version_str, our_kernel_version_str + ) + < 0 + ): + event.info( + messages.KERNEL_DOWNGRADE_WARNING.format( + current_version=our_kernel_version_str, + new_version=fips_kernel_version_str, + ) + ) + return util.prompt_for_confirmation( + msg=messages.PROMPT_YES_NO, + assume_yes=self.assume_yes, + ) + else: + LOG.warning( + "Cannot gather kernel information for '%s' and '%s'", + our_full_kernel_str, + fips_kernel_version_str, + ) + return True + def install_packages( self, + progress: api.ProgressWrapper, package_list: Optional[List[str]] = None, cleanup_on_failure: bool = True, - verbose: bool = True, ) -> None: """Install contract recommended packages for the entitlement. :param package_list: Optional package list to use instead of self.packages. :param cleanup_on_failure: Cleanup apt files if apt install fails. - :param verbose: If true, print messages to stdout """ - if verbose: - event.info( - messages.INSTALLING_SERVICE_PACKAGES.format(title=self.title) - ) - # We need to guarantee that the metapackage is installed. # While the other packages should still be installed, if they # fail, we should not block the enable operation. mandatory_packages = self.packages - super().install_packages( - package_list=mandatory_packages, verbose=False - ) + if mandatory_packages: + super().install_packages( + progress, + package_list=mandatory_packages, + ) + else: + # then this won't get printed by install_packages, so do it here + # instead + progress.progress( + messages.INSTALLING_SERVICE_PACKAGES.format(title=self.title) + ) # Any conditional packages should still be installed, but if # they fail to install we should not block the enable operation. @@ -195,15 +248,30 @@ for pkg in desired_packages: try: - super().install_packages( - package_list=[pkg], cleanup_on_failure=False, verbose=False + apt.run_apt_install_command( + packages=[pkg], + override_env_vars={"DEBIAN_FRONTEND": "noninteractive"}, + apt_options=[ + "--allow-downgrades", + '-o Dpkg::Options::="--force-confdef"', + '-o Dpkg::Options::="--force-confold"', + ], ) except exceptions.UbuntuProError: - event.info( + progress.emit( + "info", messages.FIPS_PACKAGE_NOT_AVAILABLE.format( service=self.title, pkg=pkg - ) + ), ) + if self._check_for_reboot(): + notices.add( + Notice.FIPS_SYSTEM_REBOOT_REQUIRED, + ) + + def _check_for_reboot(self) -> bool: + """Check if system needs to be rebooted because of this service.""" + return system.should_reboot() def _check_for_reboot_msg( self, operation: str, silent: bool = False @@ -213,7 +281,7 @@ @param operation: The operation being executed. @param silent: Boolean set True to silence print/log of messages """ - reboot_required = system.should_reboot() + reboot_required = self._check_for_reboot() event.needs_reboot(reboot_required) if reboot_required: if not silent: @@ -222,11 +290,7 @@ operation=operation ) ) - if operation == "install": - notices.add( - Notice.FIPS_SYSTEM_REBOOT_REQUIRED, - ) - elif operation == "disable operation": + if operation == "disable operation": notices.add( Notice.FIPS_DISABLE_REBOOT_REQUIRED, ) @@ -346,8 +410,8 @@ messages.DISABLE_FAILED_TMPL.format(title=self.title), ) - def _perform_enable(self, silent: bool = False) -> bool: - if super()._perform_enable(silent=silent): + def _perform_enable(self, progress: api.ProgressWrapper) -> bool: + if super()._perform_enable(progress): notices.remove( Notice.WRONG_FIPS_METAPACKAGE_ON_CLOUD, ) @@ -357,7 +421,7 @@ return False - def setup_apt_config(self, silent: bool = False) -> None: + def setup_apt_config(self, progress: api.ProgressWrapper) -> None: """Setup apt config based on the resourceToken and directives. FIPS-specifically handle apt-mark unhold @@ -382,7 +446,7 @@ command=" ".join(unhold_cmd) ), ) - super().setup_apt_config(silent=silent) + super().setup_apt_config(progress) class FIPSEntitlement(FIPSCommonEntitlement): @@ -394,18 +458,18 @@ pre_enable_msg = messages.PROMPT_FIPS_PRE_ENABLE @property - def incompatible_services(self) -> Tuple[IncompatibleService, ...]: + def incompatible_services(self) -> Tuple[EntitlementWithMessage, ...]: from uaclient.entitlements.livepatch import LivepatchEntitlement from uaclient.entitlements.realtime import RealtimeKernelEntitlement return ( - IncompatibleService( + EntitlementWithMessage( LivepatchEntitlement, messages.LIVEPATCH_INVALIDATES_FIPS ), - IncompatibleService( + EntitlementWithMessage( FIPSUpdatesEntitlement, messages.FIPS_UPDATES_INVALIDATES_FIPS ), - IncompatibleService( + EntitlementWithMessage( RealtimeKernelEntitlement, messages.REALTIME_FIPS_INCOMPATIBLE ), ) @@ -478,11 +542,19 @@ {"msg": pre_enable_prompt, "assume_yes": self.assume_yes}, ) ], + "pre_install": [ + ( + self.prompt_if_kernel_downgrade, + { + "assume_yes": self.assume_yes, + }, + ) + ], "post_enable": post_enable, "pre_disable": pre_disable, } - def _perform_enable(self, silent: bool = False) -> bool: + def _perform_enable(self, progress: api.ProgressWrapper) -> bool: cloud_type, error = get_cloud_type() if cloud_type is None and error == NoCloudTypeReason.CLOUD_ID_ERROR: LOG.warning( @@ -490,7 +562,7 @@ "defaulting to generic FIPS package." ) event.info(messages.FIPS_COULD_NOT_DETERMINE_CLOUD_DEFAULT_PACKAGE) - if super()._perform_enable(silent=silent): + if super()._perform_enable(progress): notices.remove( Notice.FIPS_INSTALL_OUT_OF_DATE, ) @@ -507,14 +579,14 @@ help_text = messages.FIPS_UPDATES_HELP_TEXT @property - def incompatible_services(self) -> Tuple[IncompatibleService, ...]: + def incompatible_services(self) -> Tuple[EntitlementWithMessage, ...]: from uaclient.entitlements.realtime import RealtimeKernelEntitlement return ( - IncompatibleService( + EntitlementWithMessage( FIPSEntitlement, messages.FIPS_INVALIDATES_FIPS_UPDATES ), - IncompatibleService( + EntitlementWithMessage( RealtimeKernelEntitlement, messages.REALTIME_FIPS_UPDATES_INCOMPATIBLE, ), @@ -554,20 +626,20 @@ {"msg": pre_enable_prompt, "assume_yes": self.assume_yes}, ) ], + "pre_install": [ + ( + self.prompt_if_kernel_downgrade, + { + "assume_yes": self.assume_yes, + }, + ) + ], "post_enable": post_enable, "pre_disable": pre_disable, } - def _perform_enable(self, silent: bool = False) -> bool: - if super()._perform_enable(silent=silent): - services_once_enabled = ( - self.cfg.read_cache("services-once-enabled") or {} - ) - services_once_enabled.update({self.name: True}) - self.cfg.write_cache( - key="services-once-enabled", content=services_once_enabled - ) - + def _perform_enable(self, progress: api.ProgressWrapper) -> bool: + if super()._perform_enable(progress=progress): services_once_enabled_file.write( ServicesOnceEnabledData(fips_updates=True) ) @@ -586,9 +658,9 @@ repo_key_file = "ubuntu-pro-fips-preview.gpg" @property - def incompatible_services(self) -> Tuple[IncompatibleService, ...]: + def incompatible_services(self) -> Tuple[EntitlementWithMessage, ...]: return super().incompatible_services + ( - IncompatibleService( + EntitlementWithMessage( FIPSEntitlement, messages.FIPS_INVALIDATES_FIPS_UPDATES ), ) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/landscape.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/landscape.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/landscape.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/landscape.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,7 +1,7 @@ import logging from typing import Any, Dict, Optional, Tuple -from uaclient import event_logger, exceptions, messages, system, util +from uaclient import api, event_logger, exceptions, messages, system, util from uaclient.entitlements.base import UAEntitlement from uaclient.entitlements.entitlement_status import ApplicationStatus @@ -16,13 +16,19 @@ help_doc_url = messages.urls.LANDSCAPE_HOME_PAGE help_text = messages.LANDSCAPE_HELP_TEXT - def _perform_enable(self, silent: bool = False) -> bool: + def enable_steps(self) -> int: + return 1 + + def disable_steps(self) -> int: + return 1 + + def _perform_enable(self, progress: api.ProgressWrapper) -> bool: cmd = ["landscape-config"] + self.extra_args if self.assume_yes and "--silent" not in cmd: cmd += ["--silent"] LOG.debug("Executing: %r", cmd) - event.info( + progress.progress( util.redact_sensitive_logs( messages.EXECUTING_COMMAND.format(command=" ".join(cmd)) ) @@ -30,38 +36,27 @@ try: system.subp(cmd, pipe_stdouterr=self.assume_yes) except exceptions.ProcessExecutionError as e: + LOG.exception(e) if self.assume_yes: - err_msg = messages.LANDSCAPE_CONFIG_FAILED - event.error( - err_msg.msg, - err_msg.name, - service=self.name, - additional_info={ - "stdout": e.stdout.strip(), - "stderr": e.stderr.strip(), - }, + progress.emit("info", e.stderr.strip()) + raise exceptions.LandscapeConfigFailed( + stdout=e.stdout.strip(), stderr=e.stderr.strip() ) - event.info(e.stderr.strip()) - event.info(messages.ENABLE_FAILED.format(title=self.title)) return False - - if self.assume_yes: - # when silencing landscape-config, include a success message - # otherwise, let landscape-config say what happened - event.info(messages.ENABLED_TMPL.format(title=self.title)) return True - def _perform_disable(self, silent: bool = False) -> bool: + def _perform_disable(self, progress: api.ProgressWrapper) -> bool: cmd = ["landscape-config", "--disable"] - event.info(messages.EXECUTING_COMMAND.format(command=" ".join(cmd))) + progress.progress( + messages.EXECUTING_COMMAND.format(command=" ".join(cmd)) + ) try: system.subp(cmd) except exceptions.ProcessExecutionError as e: LOG.error(e) - event.info(str(e).strip()) - event.warning(str(e), self.name) + progress.emit("info", str(e).strip()) - event.info(messages.LANDSCAPE_CONFIG_REMAINS) + progress.emit("info", messages.LANDSCAPE_CONFIG_REMAINS) return True diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/livepatch.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/livepatch.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/livepatch.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/livepatch.py 2024-04-23 13:37:02.000000000 +0000 @@ -2,6 +2,7 @@ from typing import Any, Dict, Optional, Tuple from uaclient import ( + api, event_logger, exceptions, http, @@ -11,7 +12,7 @@ system, util, ) -from uaclient.entitlements.base import IncompatibleService, UAEntitlement +from uaclient.entitlements.base import EntitlementWithMessage, UAEntitlement from uaclient.entitlements.entitlement_status import ApplicationStatus from uaclient.types import StaticAffordance @@ -41,15 +42,15 @@ affordance_check_arch = True @property - def incompatible_services(self) -> Tuple[IncompatibleService, ...]: + def incompatible_services(self) -> Tuple[EntitlementWithMessage, ...]: from uaclient.entitlements.fips import FIPSEntitlement from uaclient.entitlements.realtime import RealtimeKernelEntitlement return ( - IncompatibleService( + EntitlementWithMessage( FIPSEntitlement, messages.LIVEPATCH_INVALIDATES_FIPS ), - IncompatibleService( + EntitlementWithMessage( RealtimeKernelEntitlement, messages.REALTIME_LIVEPATCH_INCOMPATIBLE, ), @@ -81,30 +82,42 @@ ), ) - def _perform_enable(self, silent: bool = False) -> bool: + def enable_steps(self) -> int: + return 2 + + def disable_steps(self) -> int: + return 1 + + def _perform_enable(self, progress: api.ProgressWrapper) -> bool: """Enable specific entitlement. @return: True on success, False otherwise. """ + progress.progress(messages.INSTALLING_LIVEPATCH) + if not snap.is_snapd_installed(): - event.info(messages.INSTALLING_PACKAGES.format(packages="snapd")) + progress.emit( + "info", messages.INSTALLING_PACKAGES.format(packages="snapd") + ) snap.install_snapd() if not snap.is_snapd_installed_as_a_snap(): - event.info( - messages.INSTALLING_PACKAGES.format(packages="snapd snap") + progress.emit( + "info", + messages.INSTALLING_PACKAGES.format(packages="snapd snap"), ) try: snap.install_snap("snapd") except exceptions.ProcessExecutionError as e: LOG.warning("Failed to install snapd as a snap", exc_info=e) - event.info( + progress.emit( + "info", messages.EXECUTING_COMMAND_FAILED.format( command="snap install snapd" - ) + ), ) - snap.run_snapd_wait_cmd() + snap.run_snapd_wait_cmd(progress) try: snap.refresh_snap("snapd") @@ -128,10 +141,11 @@ retry_sleeps=snap.SNAP_INSTALL_RETRIES, ) if not livepatch.is_livepatch_installed(): - event.info( + progress.emit( + "info", messages.INSTALLING_PACKAGES.format( packages="canonical-livepatch snap" - ) + ), ) try: snap.install_snap("canonical-livepatch") @@ -141,11 +155,14 @@ livepatch.configure_livepatch_proxy(http_proxy, https_proxy) return self.setup_livepatch_config( - process_directives=True, process_token=True + progress, process_directives=True, process_token=True ) def setup_livepatch_config( - self, process_directives: bool = True, process_token: bool = True + self, + progress: api.ProgressWrapper, + process_directives: bool = True, + process_token: bool = True, ) -> bool: """Processs configuration setup for livepatch directives. @@ -154,6 +171,8 @@ :param process_token: Boolean set True when token should be processsed. """ + progress.progress(messages.SETTING_UP_LIVEPATCH) + entitlement_cfg = self.cfg.machine_token_file.entitlements.get( self.name ) @@ -162,10 +181,11 @@ process_config_directives(entitlement_cfg) except exceptions.ProcessExecutionError as e: LOG.error(str(e), exc_info=e) - event.info( + progress.emit( + "info", messages.LIVEPATCH_UNABLE_TO_CONFIGURE.format( error_msg=str(e) - ) + ), ) return False if process_token: @@ -180,7 +200,7 @@ application_status, _details = self.application_status() if application_status != ApplicationStatus.DISABLED: LOG.info("Disabling livepatch before re-enabling") - event.info(messages.LIVEPATCH_DISABLE_REATTACH) + progress.emit("info", messages.LIVEPATCH_DISABLE_REATTACH) try: system.subp([livepatch.LIVEPATCH_CMD, "disable"]) except exceptions.ProcessExecutionError as e: @@ -199,21 +219,22 @@ break if msg == messages.LIVEPATCH_UNABLE_TO_ENABLE: msg += str(e) - event.info(msg) + progress.emit("info", msg) return False - event.info( - messages.ENABLED_TMPL.format(title="Canonical Livepatch") - ) return True - def _perform_disable(self, silent=False): + def _perform_disable(self, progress: api.ProgressWrapper): """Disable specific entitlement @return: True on success, False otherwise. """ if not livepatch.is_livepatch_installed(): return True - system.subp([livepatch.LIVEPATCH_CMD, "disable"], capture=True) + cmd = [livepatch.LIVEPATCH_CMD, "disable"] + progress.progress( + messages.EXECUTING_COMMAND.format(command=" ".join(cmd)) + ) + system.subp(cmd, capture=True) return True def application_status( @@ -306,11 +327,11 @@ delta_entitlement = deltas.get("entitlement", {}) process_enable_default = delta_entitlement.get("obligations", {}).get( - "enabledByDefault", False + "enableByDefault", False ) if process_enable_default: - enable_success, _ = self.enable() + enable_success, _ = self.enable(api.ProgressWrapper()) return enable_success application_status, _ = self.application_status() @@ -333,6 +354,7 @@ ) ) return self.setup_livepatch_config( + progress=api.ProgressWrapper(), process_directives=process_directives, process_token=process_token, ) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/realtime.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/realtime.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/realtime.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/realtime.py 2024-05-10 17:07:05.000000000 +0000 @@ -2,7 +2,7 @@ from uaclient import apt, event_logger, messages, system, util from uaclient.entitlements import repo -from uaclient.entitlements.base import IncompatibleService, UAEntitlement +from uaclient.entitlements.base import EntitlementWithMessage, UAEntitlement from uaclient.types import ( # noqa: F401 MessagingOperations, MessagingOperationsDict, @@ -42,7 +42,7 @@ } @property - def incompatible_services(self) -> Tuple[IncompatibleService, ...]: + def incompatible_services(self) -> Tuple[EntitlementWithMessage, ...]: from uaclient.entitlements.fips import ( FIPSEntitlement, FIPSUpdatesEntitlement, @@ -50,14 +50,14 @@ from uaclient.entitlements.livepatch import LivepatchEntitlement return ( - IncompatibleService( + EntitlementWithMessage( FIPSEntitlement, messages.REALTIME_FIPS_INCOMPATIBLE ), - IncompatibleService( + EntitlementWithMessage( FIPSUpdatesEntitlement, messages.REALTIME_FIPS_UPDATES_INCOMPATIBLE, ), - IncompatibleService( + EntitlementWithMessage( LivepatchEntitlement, messages.REALTIME_LIVEPATCH_INCOMPATIBLE ), ) @@ -119,10 +119,10 @@ class RealtimeVariant(RealtimeKernelEntitlement): @property - def incompatible_services(self) -> Tuple[IncompatibleService, ...]: + def incompatible_services(self) -> Tuple[EntitlementWithMessage, ...]: incompatible_variants = tuple( [ - IncompatibleService( + EntitlementWithMessage( cls, messages.REALTIME_VARIANT_INCOMPATIBLE.format( service=self.title, variant=cls.title @@ -151,7 +151,7 @@ class RaspberryPiRealtime(RealtimeVariant): - variant_name = "rpi" + variant_name = "raspi" title = messages.REALTIME_RASPI_TITLE description = messages.REALTIME_RASPI_DESCRIPTION is_variant = True diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/repo.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/repo.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/repo.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/repo.py 2024-04-23 13:37:02.000000000 +0000 @@ -6,6 +6,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union from uaclient import ( + api, apt, contract, event_logger, @@ -21,6 +22,7 @@ CanDisableFailure, CanDisableFailureReason, ) +from uaclient.files.state_files import status_cache_file event = event_logger.get_event_logger() LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) @@ -62,6 +64,10 @@ return self.repo_file_tmpl.format(name=self.name, extension=extension) @property + def repo_policy_check_tmpl(self) -> str: + return self.repo_url_tmpl + " {}" + + @property def packages(self) -> List[str]: """debs to install on enablement""" packages = [] @@ -78,6 +84,22 @@ return packages + @property + def apt_url(self) -> Optional[str]: + return ( + self.entitlement_cfg.get("entitlement", {}) + .get("directives", {}) + .get("aptURL") + ) + + @property + def apt_suites(self) -> Optional[str]: + return ( + self.entitlement_cfg.get("entitlement", {}) + .get("directives", {}) + .get("suites") + ) + def _check_for_reboot(self) -> bool: """Check if system needs to be rebooted.""" reboot_required = system.should_reboot( @@ -113,38 +135,58 @@ return result, reason - def _perform_enable(self, silent: bool = False) -> bool: + def enable_steps(self) -> int: + will_install = self.packages is not None and len(self.packages) > 0 + if self.access_only or not will_install: + # 1. Configure APT + # 2. Update APT lists + return 2 + else: + # 3. Install packages + return 3 + + def _perform_enable(self, progress: api.ProgressWrapper) -> bool: """Enable specific entitlement. @return: True on success, False otherwise. @raises: UbuntuProError on failure to install suggested packages """ - self.setup_apt_config(silent=silent) + progress.progress( + messages.CONFIGURING_APT_ACCESS.format(service=self.title) + ) + self.setup_apt_config(progress) if self.supports_access_only and self.access_only: if len(self.packages) > 0: - event.info( + progress.emit( + "info", messages.SKIPPING_INSTALLING_PACKAGES.format( packages=" ".join(self.packages) - ) + ), ) - event.info(messages.ACCESS_ENABLED_TMPL.format(title=self.title)) else: - self.install_packages() - event.info(messages.ENABLED_TMPL.format(title=self.title)) - self._check_for_reboot_msg(operation="install") + self.install_packages(progress) return True - def _perform_disable(self, silent=False): + def disable_steps(self) -> int: + if not self.purge: + # 1. Unconfigure APT + # 2. Update package lists + return 2 + else: + # 3. Purge + return 3 + + def _perform_disable(self, progress: api.ProgressWrapper): if self.purge and self.origin: - print(messages.PURGE_EXPERIMENTAL) - print() + progress.emit("info", messages.PURGE_EXPERIMENTAL) + progress.emit("info", "") repo_origin_packages = apt.get_installed_packages_by_origin( self.origin ) - if not self.purge_kernel_check(repo_origin_packages): + if not self.purge_kernel_check(repo_origin_packages, progress): return False packages_to_reinstall = [] @@ -163,20 +205,23 @@ packages_to_remove.append(package) if not self.prompt_for_purge( - packages_to_remove, packages_to_reinstall + packages_to_remove, packages_to_reinstall, progress ): return False if hasattr(self, "remove_packages"): self.remove_packages() - self.remove_apt_config(silent=silent) + self.remove_apt_config(progress) if self.purge and self.origin: + progress.progress( + messages.PURGING_PACKAGES.format(title=self.title) + ) self.execute_reinstall(packages_to_reinstall) self.execute_removal(packages_to_remove) return True - def purge_kernel_check(self, package_list): + def purge_kernel_check(self, package_list, progress: api.ProgressWrapper): """ Checks if the purge operation involves a kernel. @@ -197,14 +242,24 @@ if m: linux_image_versions.append(m.group(1)) if linux_image_versions: - print(messages.PURGE_KERNEL_REMOVAL.format(service=self.title)) - print(" ".join(linux_image_versions)) + # A kernel needs to be removed to purge + # API will fail here, but we want CLI to allow it to continue + # after a prompt + if not progress.is_interactive(): + raise exceptions.NonInteractiveKernelPurgeDisallowed() + + progress.emit( + "info", + messages.PURGE_KERNEL_REMOVAL.format(service=self.title), + ) + progress.emit("info", " ".join(linux_image_versions)) current_kernel = system.get_kernel_info().uname_release - print( + progress.emit( + "info", messages.PURGE_CURRENT_KERNEL.format( kernel_version=current_kernel - ) + ), ) installed_kernels = system.get_installed_ubuntu_kernels() @@ -216,34 +271,58 @@ ] if not alternative_kernels: - print(messages.PURGE_NO_ALTERNATIVE_KERNEL) + progress.emit("info", messages.PURGE_NO_ALTERNATIVE_KERNEL) return False - if not util.prompt_for_confirmation( - messages.PURGE_KERNEL_CONFIRMATION - ): - return False + progress.emit( + "message_operation", + [ + ( + util.prompt_for_confirmation, + {"msg": messages.PURGE_KERNEL_CONFIRMATION}, + ) + ], + ) return True - def prompt_for_purge(self, packages_to_remove, packages_to_reinstall): + def prompt_for_purge( + self, + packages_to_remove, + packages_to_reinstall, + progress: api.ProgressWrapper, + ): prompt = False if packages_to_remove: - print(messages.WARN_PACKAGES_REMOVAL) - util.print_package_list( - [package.name for package in packages_to_remove] + progress.emit("info", messages.WARN_PACKAGES_REMOVAL) + progress.emit( + "info", + util.create_package_list_str( + [package.name for package in packages_to_remove] + ), ) prompt = True if packages_to_reinstall: - print(messages.WARN_PACKAGES_REINSTALL) - util.print_package_list( - [package.name for (package, _) in packages_to_reinstall] + progress.emit("info", messages.WARN_PACKAGES_REINSTALL) + progress.emit( + "info", + util.create_package_list_str( + [package.name for (package, _) in packages_to_reinstall] + ), ) prompt = True if prompt: - return util.prompt_for_confirmation(messages.PROCEED_YES_NO) + progress.emit( + "message_operation", + [ + ( + util.prompt_for_confirmation, + {"msg": messages.PROCEED_YES_NO}, + ) + ], + ) return True def execute_removal(self, packages_to_remove): @@ -296,14 +375,25 @@ ApplicationStatus.DISABLED, messages.NO_APT_URL_FOR_SERVICE.format(title=self.title), ) - policy = apt.get_apt_cache_policy(error_msg=messages.APT_POLICY_FAILED) - match = re.search(self.repo_url_tmpl.format(repo_url), policy) - if match: - current_status = ( - ApplicationStatus.ENABLED, - messages.SERVICE_IS_ACTIVE.format(title=self.title), + repo_suites = directives.get("suites") + if not repo_suites: + return ( + ApplicationStatus.DISABLED, + messages.NO_SUITES_FOR_SERVICE.format(title=self.title), ) + policy = apt.get_apt_cache_policy(error_msg=messages.APT_POLICY_FAILED) + for suite in repo_suites: + service_match = re.search( + self.repo_policy_check_tmpl.format(repo_url, suite), policy + ) + if service_match: + current_status = ( + ApplicationStatus.ENABLED, + messages.SERVICE_IS_ACTIVE.format(title=self.title), + ) + break + if self.check_packages_are_installed: for package in self.packages: if not apt.is_installed(package): @@ -367,7 +457,7 @@ delta_directives = delta_entitlement.get("directives", {}) delta_apt_url = delta_directives.get("aptURL") delta_packages = delta_directives.get("additionalPackages") - status_cache = self.cfg.read_cache("status-cache") + status_cache = status_cache_file.read() if delta_directives and status_cache: application_status = self._check_application_status_on_cache() @@ -393,8 +483,8 @@ # Remove original aptURL and auth and rewrite apt.remove_auth_apt_repo(self.repo_file, old_url) - self.remove_apt_config() - self.setup_apt_config() + self.remove_apt_config(api.ProgressWrapper()) + self.setup_apt_config(api.ProgressWrapper()) if delta_packages: LOG.info("New additionalPackages, installing %r", delta_packages) @@ -403,22 +493,23 @@ packages=", ".join(delta_packages) ) ) - self.install_packages(package_list=delta_packages) + self.install_packages( + api.ProgressWrapper(), package_list=delta_packages + ) return True def install_packages( self, + progress: api.ProgressWrapper, package_list: Optional[List[str]] = None, cleanup_on_failure: bool = True, - verbose: bool = True, ) -> None: """Install contract recommended packages for the entitlement. :param package_list: Optional package list to use instead of self.packages. :param cleanup_on_failure: Cleanup apt files if apt install fails. - :param verbose: If true, print messages to stdout """ if not package_list: @@ -427,21 +518,18 @@ if not package_list: return - msg_ops = self.messaging.get("pre_install", []) - if not util.handle_message_operations(msg_ops): - return + progress.emit("message_operation", self.messaging.get("pre_install")) try: - self._update_sources_list() + self._update_sources_list(progress) except exceptions.UbuntuProError: if cleanup_on_failure: - self.remove_apt_config() + self.remove_apt_config(api.ProgressWrapper()) raise - if verbose: - event.info( - messages.INSTALLING_SERVICE_PACKAGES.format(title=self.title) - ) + progress.progress( + messages.INSTALLING_SERVICE_PACKAGES.format(title=self.title) + ) if self.apt_noninteractive: override_env_vars = {"DEBIAN_FRONTEND": "noninteractive"} @@ -461,12 +549,16 @@ override_env_vars=override_env_vars, ) except exceptions.UbuntuProError: - event.info(messages.ENABLE_FAILED.format(title=self.title)) if cleanup_on_failure: - self.remove_apt_config() + LOG.info( + "Apt install failed, removing apt config for {}".format( + self.name + ) + ) + self.remove_apt_config(api.ProgressWrapper()) raise - def setup_apt_config(self, silent: bool = False) -> None: + def setup_apt_config(self, progress: api.ProgressWrapper) -> None: """Setup apt config based on the resourceToken and directives. Also sets up apt proxy if necessary. @@ -558,16 +650,16 @@ prerequisite_pkgs.append("ca-certificates") if prerequisite_pkgs: - if not silent: - event.info( - messages.INSTALLING_PACKAGES.format( - packages=", ".join(prerequisite_pkgs) - ) - ) + progress.emit( + "info", + messages.INSTALLING_PACKAGES.format( + packages=", ".join(prerequisite_pkgs) + ), + ) try: apt.run_apt_install_command(packages=prerequisite_pkgs) except exceptions.UbuntuProError: - self.remove_apt_config() + self.remove_apt_config(api.ProgressWrapper()) raise apt.add_auth_apt_repo( repo_filename, @@ -580,16 +672,17 @@ # probably wants access to the repo that was just enabled. # Side-effect is that apt policy will now report the repo as accessible # which allows pro status to report correct info - if not silent: - event.info(messages.APT_UPDATING_LIST.format(name=self.title)) + progress.progress(messages.APT_UPDATING_LIST.format(name=self.title)) try: apt.update_sources_list(repo_filename) except exceptions.UbuntuProError: - self.remove_apt_config(run_apt_update=False) + self.remove_apt_config(api.ProgressWrapper(), run_apt_update=False) raise def remove_apt_config( - self, run_apt_update: bool = True, silent: bool = False + self, + progress: api.ProgressWrapper, + run_apt_update: bool = True, ): """Remove any repository apt configuration files. @@ -606,6 +699,9 @@ if not repo_url: raise exceptions.MissingAptURLDirective(entitlement_name=self.name) + progress.progress( + messages.REMOVING_APT_CONFIGURATION.format(title=self.title) + ) apt.remove_auth_apt_repo(repo_filename, repo_url, self.repo_key_file) apt.remove_apt_list_files(repo_url, series) @@ -614,6 +710,5 @@ system.ensure_file_absent(repo_pref_file) if run_apt_update: - if not silent: - event.info(messages.APT_UPDATING_LISTS) + progress.progress(messages.APT_UPDATING_LISTS) apt.run_apt_update_command() diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/ros.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/ros.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/ros.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/ros.py 2024-04-23 13:37:02.000000000 +0000 @@ -2,7 +2,7 @@ from uaclient import messages from uaclient.entitlements import repo -from uaclient.entitlements.base import UAEntitlement +from uaclient.entitlements.base import EntitlementWithMessage, UAEntitlement class ROSCommonEntitlement(repo.RepoEntitlement): @@ -18,15 +18,21 @@ origin = "UbuntuROS" @property - def required_services(self) -> Tuple[Type[UAEntitlement], ...]: + def required_services(self) -> Tuple[EntitlementWithMessage, ...]: from uaclient.entitlements.esm import ( ESMAppsEntitlement, ESMInfraEntitlement, ) return ( - ESMInfraEntitlement, - ESMAppsEntitlement, + EntitlementWithMessage( + ESMInfraEntitlement, + messages.ROS_REQUIRES_ESM, + ), + EntitlementWithMessage( + ESMAppsEntitlement, + messages.ROS_REQUIRES_ESM, + ), ) @property @@ -42,14 +48,23 @@ origin = "UbuntuROSUpdates" @property - def required_services(self) -> Tuple[Type[UAEntitlement], ...]: + def required_services(self) -> Tuple[EntitlementWithMessage, ...]: from uaclient.entitlements.esm import ( ESMAppsEntitlement, ESMInfraEntitlement, ) return ( - ESMInfraEntitlement, - ESMAppsEntitlement, - ROSEntitlement, + EntitlementWithMessage( + ESMInfraEntitlement, + messages.ROS_REQUIRES_ESM, + ), + EntitlementWithMessage( + ESMAppsEntitlement, + messages.ROS_REQUIRES_ESM, + ), + EntitlementWithMessage( + ROSEntitlement, + messages.ROS_UPDATES_REQUIRES_ROS, + ), ) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/conftest.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/conftest.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/conftest.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/conftest.py 2024-04-23 13:37:02.000000000 +0000 @@ -10,6 +10,7 @@ *, affordances: Dict[str, Any] = None, directives: Dict[str, Any] = None, + overrides: List[Dict[str, Any]] = None, entitled: bool = True, obligations: Dict[str, Any] = None, suites: List[str] = None, @@ -30,6 +31,7 @@ entitlement_type, affordances=affordances, directives=directives, + overrides=overrides, entitled=entitled, obligations=obligations, suites=suites, @@ -46,6 +48,7 @@ *, affordances: Dict[str, Any] = None, directives: Dict[str, Any] = None, + overrides: List[Dict[str, Any]] = None, entitled: bool = True, obligations: Dict[str, Any] = None, suites: List[str] = None, @@ -66,12 +69,16 @@ if additional_packages: directives["additionalPackages"] = additional_packages + if overrides is None: + overrides = [] + return { "obligations": obligations, "type": entitlement_type, "entitled": entitled, "directives": directives, "affordances": affordances, + "overrides": overrides, } @@ -92,6 +99,7 @@ affordances: Dict[str, Any] = None, directives: Dict[str, Any] = None, obligations: Dict[str, Any] = None, + overrides: List[Dict[str, Any]] = None, entitled: bool = True, allow_beta: bool = False, called_name: str = "", @@ -101,18 +109,26 @@ suites: List[str] = None, additional_packages: List[str] = None, cfg: Optional[config.UAConfig] = None, - cfg_extension: Optional[Dict[str, Any]] = None + cfg_extension: Optional[Dict[str, Any]] = None, + cfg_features: Optional[Dict[str, Any]] = None, + # Those extra args should be used for scenarios where a cls + # instance requires something that is not shared between all + # entitlement classes + extra_args: Optional[Dict[str, Any]] = None ): if not cfg: cfg_arg = {"data_dir": tmpdir.strpath} if cfg_extension is not None: cfg_arg.update(cfg_extension) + if cfg_features is not None: + cfg_arg["features"] = cfg_features cfg = FakeConfig(cfg_overrides=cfg_arg) cfg.machine_token_file.write( machine_token( cls.name, affordances=affordances, directives=directives, + overrides=overrides, obligations=obligations, entitled=entitled, suites=suites, @@ -124,9 +140,14 @@ "allow_beta": allow_beta, "called_name": called_name, "access_only": access_only, + "purge": purge, } if assume_yes is not None: args["assume_yes"] = assume_yes + + if extra_args: + args = {**args, **extra_args} + return cls(cfg, **args) return factory_func diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_base.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_base.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_base.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_base.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,7 +1,8 @@ """Tests related to uaclient.entitlement.base module.""" + import copy import logging -from typing import Any, Dict, Optional, Tuple +from functools import partial import mock import pytest @@ -30,21 +31,27 @@ cfg=None, disable=None, enable=None, - applicability_status=(ApplicabilityStatus.APPLICABLE, ""), + applicability_status=None, application_status=(ApplicationStatus.DISABLED, ""), allow_beta=False, + assume_yes=False, supports_access_only=False, access_only=False, supports_purge=False, purge=False, dependent_services=None, required_services=None, + incompatible_services=None, blocking_incompatible_services=None, variant_name="", **kwargs ): super().__init__( - cfg, allow_beta=allow_beta, access_only=access_only, purge=purge + cfg, + allow_beta=allow_beta, + assume_yes=assume_yes, + access_only=access_only, + purge=purge, ) self.supports_access_only = supports_access_only self.supports_purge = supports_purge @@ -54,21 +61,31 @@ self._application_status = application_status self._dependent_services = dependent_services self._required_services = required_services + self._incompatible_services = incompatible_services self._blocking_incompatible_services = blocking_incompatible_services self._variant_name = variant_name - def _perform_disable(self, **kwargs): + def _perform_disable(self, *args, **kwargs): self._application_status = ( ApplicationStatus.DISABLED, "disable() called", ) return self._disable - def _perform_enable(self, **kwargs): + def _perform_enable(self, *args, **kwargs): return self._enable + def enable_steps(self): + return 1 + + def disable_steps(self): + return 1 + def applicability_status(self): - return self._applicability_status + if self._applicability_status is not None: + return self._applicability_status + else: + return super().applicability_status() def application_status(self): return self._application_status @@ -89,69 +106,11 @@ @pytest.fixture -def concrete_entitlement_factory(FakeConfig): - def factory( - *, - entitled: bool = True, - applicability_status: Tuple[ApplicabilityStatus, str] = ( - ApplicabilityStatus.APPLICABLE, - "", - ), - application_status: Tuple[ApplicationStatus, str] = ( - ApplicationStatus.DISABLED, - "", - ), - feature_overrides: Optional[Dict[str, str]] = None, - allow_beta: bool = False, - supports_access_only: bool = False, - access_only: bool = False, - supports_purge: bool = False, - purge: bool = False, - enable: bool = False, - disable: bool = False, - dependent_services: Tuple[Any, ...] = None, - required_services: Tuple[Any, ...] = None, - variant_name: str = "" - ) -> ConcreteTestEntitlement: - cfg = FakeConfig() - machineToken = { - "machineToken": "blah", - "machineTokenInfo": { - "contractInfo": { - "resourceEntitlements": [ - { - "type": "testconcreteentitlement", - "entitled": entitled, - } - ] - } - }, - } - cfg.machine_token_file.write(machineToken) - - if feature_overrides: - cfg.cfg.update({"features": feature_overrides}) - - return ConcreteTestEntitlement( - cfg, - applicability_status=applicability_status, - application_status=application_status, - allow_beta=allow_beta, - supports_access_only=supports_access_only, - access_only=access_only, - supports_purge=supports_purge, - purge=purge, - enable=enable, - disable=disable, - dependent_services=dependent_services, - required_services=required_services, - variant_name=variant_name, - ) - - return factory +def base_entitlement_factory(entitlement_factory): + return partial(entitlement_factory, ConcreteTestEntitlement) -class TestUaEntitlement: +class TestEntitlement: def test_entitlement_abstract_class(self): """UAEntitlement is abstract requiring concrete methods.""" with pytest.raises(TypeError): @@ -169,7 +128,7 @@ assert "/some/path" == entitlement.cfg.data_dir -class TestUaEntitlementNames: +class TestEntitlementNames: @pytest.mark.parametrize( "p_name,expected", ( @@ -177,39 +136,33 @@ ("testconcreteentitlement", ["testconcreteentitlement"]), ), ) - @mock.patch( - "uaclient.entitlements.base.UAEntitlement.presentation_name", - new_callable=mock.PropertyMock, - ) - def test_valid_names( - self, m_p_name, p_name, expected, concrete_entitlement_factory - ): - m_p_name.return_value = p_name - entitlement = concrete_entitlement_factory(entitled=True) + def test_valid_names(self, p_name, expected, base_entitlement_factory): + entitlement = base_entitlement_factory( + entitled=True, + affordances={"presentedAs": p_name}, + ) assert expected == entitlement.valid_names - def test_presentation_name(self, concrete_entitlement_factory): - entitlement = concrete_entitlement_factory(entitled=True) + def test_presentation_name(self, entitlement_factory): + entitlement = entitlement_factory( + ConcreteTestEntitlement, + entitled=True, + ) assert "testconcreteentitlement" == entitlement.presentation_name - m_entitlements = { - "testconcreteentitlement": { - "entitlement": { - "affordances": {"presentedAs": "something_else"} - } - } - } - with mock.patch( - "uaclient.files.MachineTokenFile.entitlements", m_entitlements - ): - assert "something_else" == entitlement.presentation_name + entitlement = entitlement_factory( + ConcreteTestEntitlement, + entitled=True, + affordances={"presentedAs": "something_else"}, + ) + assert "something_else" == entitlement.presentation_name -class TestUaEntitlementCanEnable: - def test_can_enable_false_on_unentitled( - self, concrete_entitlement_factory - ): +class TestEntitlementCanEnable: + def test_can_enable_false_on_unentitled(self, base_entitlement_factory): """When entitlement contract is not enabled, can_enable is False.""" - entitlement = concrete_entitlement_factory(entitled=False) + entitlement = base_entitlement_factory( + entitled=False, + ) can_enable, reason = entitlement.can_enable() assert not can_enable @@ -222,13 +175,15 @@ ) def test_can_enable_false_on_access_only_not_supported( - self, concrete_entitlement_factory + self, base_entitlement_factory ): """When entitlement contract is not enabled, can_enable is False.""" - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - supports_access_only=False, - access_only=True, + extra_args={ + "supports_access_only": False, + "access_only": True, + }, ) can_enable, reason = entitlement.can_enable() @@ -249,28 +204,33 @@ self, m_refresh, caplog_text, - concrete_entitlement_factory, + base_entitlement_factory, ): """When entitlement contract is not enabled, can_enable is False.""" + entitlement = base_entitlement_factory( + entitled=False, + ) - ent = concrete_entitlement_factory(entitled=False) - - with mock.patch.object(ent, "is_access_expired", return_value=True): - assert not ent.can_enable()[0] + with mock.patch.object( + entitlement, "is_access_expired", return_value=True + ): + assert not entitlement.can_enable()[0] - assert [mock.call(ent.cfg)] == m_refresh.call_args_list + assert [mock.call(entitlement.cfg)] == m_refresh.call_args_list assert ( "Updating contract on service 'testconcreteentitlement' expiry" in caplog_text() ) def test_can_enable_false_on_entitlement_active( - self, concrete_entitlement_factory + self, base_entitlement_factory ): """When entitlement is ENABLED, can_enable returns False.""" - application_status = ApplicationStatus.ENABLED - entitlement = concrete_entitlement_factory( - entitled=True, application_status=(application_status, "") + entitlement = base_entitlement_factory( + entitled=True, + extra_args={ + "application_status": (ApplicationStatus.ENABLED, ""), + }, ) can_enable, reason = entitlement.can_enable() @@ -283,51 +243,60 @@ ).msg ) - def test_can_enable_false_on_entitlement_inapplicable( - self, concrete_entitlement_factory + @pytest.mark.parametrize( + "applicability_status,expected_ret,expected_reason", + ( + ( + (ApplicabilityStatus.INAPPLICABLE, "msg"), + False, + CanEnableFailure(CanEnableFailureReason.INAPPLICABLE, "msg"), + ), + ( + (ApplicabilityStatus.APPLICABLE, ""), + True, + None, + ), + ), + ) + def test_can_enable_on_entitlement_given_applicability_status( + self, + applicability_status, + expected_ret, + expected_reason, + base_entitlement_factory, ): """When entitlement is INAPPLICABLE, can_enable returns False.""" - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - applicability_status=( - ApplicabilityStatus.INAPPLICABLE, - "msg", - ), - application_status=(ApplicationStatus.DISABLED, ""), + extra_args={ + "application_status": (ApplicationStatus.DISABLED, ""), + "applicability_status": applicability_status, + }, ) can_enable, reason = entitlement.can_enable() - assert not can_enable - assert reason.reason == CanEnableFailureReason.INAPPLICABLE - assert reason.message == "msg" + assert expected_ret is can_enable - def test_can_enable_true_on_entitlement_inactive( - self, concrete_entitlement_factory - ): - """When an entitlement is applicable and disabled, we can_enable""" - entitlement = concrete_entitlement_factory( - entitled=True, - applicability_status=(ApplicabilityStatus.APPLICABLE, ""), - application_status=(ApplicationStatus.DISABLED, ""), - ) - - can_enable, reason = entitlement.can_enable() - assert can_enable - assert reason is None + if reason: + assert expected_reason.reason == reason.reason + assert expected_reason.message == reason.message + else: + assert expected_reason is None @pytest.mark.parametrize("is_beta", (True, False)) @pytest.mark.parametrize("allow_beta", (True, False)) @pytest.mark.parametrize("allow_beta_cfg", (True, False)) def test_can_enable_on_beta_service( - self, allow_beta_cfg, allow_beta, is_beta, concrete_entitlement_factory + self, allow_beta_cfg, allow_beta, is_beta, base_entitlement_factory ): - feature_overrides = {"allow_beta": allow_beta_cfg} - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - applicability_status=(ApplicabilityStatus.APPLICABLE, ""), - application_status=(ApplicationStatus.DISABLED, ""), - feature_overrides=feature_overrides, allow_beta=allow_beta, + cfg_features={"allow_beta": allow_beta_cfg}, + extra_args={ + "applicability_status": (ApplicabilityStatus.APPLICABLE, ""), + "application_status": (ApplicationStatus.DISABLED, ""), + }, ) entitlement.is_beta = is_beta can_enable, reason = entitlement.can_enable() @@ -341,51 +310,50 @@ assert reason.message is None def test_can_enable_when_incompatible_service_found( - self, concrete_entitlement_factory + self, base_entitlement_factory, mock_entitlement ): - base_ent = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - applicability_status=(ApplicabilityStatus.APPLICABLE, ""), - application_status=(ApplicationStatus.DISABLED, ""), + extra_args={ + "applicability_status": (ApplicabilityStatus.APPLICABLE, ""), + "application_status": (ApplicationStatus.DISABLED, ""), + }, + ) + m_incompatible_cls, _ = mock_entitlement( + application_status=(ApplicationStatus.ENABLED, "") ) - m_entitlement_cls = mock.MagicMock() - m_entitlement_obj = m_entitlement_cls.return_value - m_entitlement_obj.application_status.return_value = [ - ApplicationStatus.ENABLED, - "", - ] - base_ent._incompatible_services = ( - base.IncompatibleService( - m_entitlement_cls, messages.NamedMessage("test", "test") + entitlement._incompatible_services = ( + base.EntitlementWithMessage( + m_incompatible_cls, messages.NamedMessage("test", "test") ), ) - ret, reason = base_ent.can_enable() + ret, reason = entitlement.can_enable() assert ret is False assert reason.reason == CanEnableFailureReason.INCOMPATIBLE_SERVICE assert reason.message is None def test_can_enable_when_required_service_found( - self, concrete_entitlement_factory + self, base_entitlement_factory, mock_entitlement ): - m_ent_cls = mock.MagicMock() - type(m_ent_cls).name = mock.PropertyMock(return_value="test") - m_ent_obj = m_ent_cls.return_value - m_ent_obj.application_status.return_value = [ - ApplicationStatus.DISABLED, - "", - ] - type(m_ent_obj).title = mock.PropertyMock(return_value="test") - base_ent = concrete_entitlement_factory( + m_required_service_cls, _ = mock_entitlement( + application_status=(ApplicationStatus.DISABLED, "") + ) + + entitlement = base_entitlement_factory( entitled=True, - applicability_status=(ApplicabilityStatus.APPLICABLE, ""), - application_status=(ApplicationStatus.DISABLED, ""), - required_services=(m_ent_cls,), + extra_args={ + "applicability_status": (ApplicabilityStatus.APPLICABLE, ""), + "application_status": (ApplicationStatus.DISABLED, ""), + "required_services": ( + mock.MagicMock(entitlement=m_required_service_cls), + ), + }, ) - ret, reason = base_ent.can_enable() + ret, reason = entitlement.can_enable() assert ret is False assert ( @@ -394,51 +362,45 @@ assert reason.message is None -class TestUaEntitlementEnable: +class TestEntitlementEnable: @pytest.mark.parametrize( "block_disable_on_enable,assume_yes", [(False, False), (False, True), (True, False), (True, True)], ) @mock.patch("uaclient.util.is_config_value_true") - @mock.patch("uaclient.util.prompt_for_confirmation") def test_enable_when_incompatible_service_found( self, - m_prompt, m_is_config_value_true, block_disable_on_enable, assume_yes, - concrete_entitlement_factory, + base_entitlement_factory, + mock_entitlement, ): - m_prompt.return_value = assume_yes m_is_config_value_true.return_value = block_disable_on_enable - base_ent = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - enable=True, - applicability_status=(ApplicabilityStatus.APPLICABLE, ""), - application_status=(ApplicationStatus.DISABLED, ""), + extra_args={ + "applicability_status": (ApplicabilityStatus.APPLICABLE, ""), + "application_status": (ApplicationStatus.DISABLED, ""), + "enable": True, + }, ) - m_entitlement_cls = mock.MagicMock() - m_entitlement_obj = m_entitlement_cls.return_value - m_entitlement_obj.application_status.return_value = [ - ApplicationStatus.ENABLED, - "", - ] - base_ent._incompatible_services = ( - base.IncompatibleService( - m_entitlement_cls, messages.NamedMessage("test", "test") + m_incompatible_cls, m_incompatible_obj = mock_entitlement( + application_status=(ApplicationStatus.ENABLED, ""), + disable=(True, None), + ) + entitlement._incompatible_services = ( + base.EntitlementWithMessage( + m_incompatible_cls, messages.NamedMessage("test", "test") ), ) - ret, reason = base_ent.enable() - - expected_prompt_call = 1 - if block_disable_on_enable: - expected_prompt_call = 0 + ret, reason = entitlement.enable(mock.MagicMock()) expected_ret = False expected_reason = CanEnableFailureReason.INCOMPATIBLE_SERVICE - if assume_yes and not block_disable_on_enable: + if not block_disable_on_enable: expected_ret = True expected_reason = None expected_disable_call = 1 if expected_ret else 0 @@ -448,52 +410,35 @@ assert reason is None else: assert reason.reason == expected_reason - assert m_prompt.call_count == expected_prompt_call assert m_is_config_value_true.call_count == 1 - assert m_entitlement_obj.disable.call_count == expected_disable_call + assert m_incompatible_obj.disable.call_count == expected_disable_call @pytest.mark.parametrize("assume_yes", ((False), (True))) - @mock.patch("uaclient.util.prompt_for_confirmation") def test_enable_when_required_service_found( - self, m_prompt, assume_yes, concrete_entitlement_factory + self, assume_yes, base_entitlement_factory, mock_entitlement ): - m_prompt.return_value = assume_yes - - m_ent_cls = mock.MagicMock() - type(m_ent_cls).name = mock.PropertyMock(return_value="test") - m_ent_obj = m_ent_cls.return_value - m_ent_obj.application_status.return_value = [ - ApplicationStatus.DISABLED, - "", - ] - m_ent_obj.enable.return_value = (True, "") - type(m_ent_obj).title = mock.PropertyMock(return_value="test") - - base_ent = concrete_entitlement_factory( - entitled=True, - enable=True, - applicability_status=(ApplicabilityStatus.APPLICABLE, ""), + m_required_service_cls, m_required_service_obj = mock_entitlement( application_status=(ApplicationStatus.DISABLED, ""), - required_services=(m_ent_cls,), + enable=(True, None), ) - ret, reason = base_ent.enable() + entitlement = base_entitlement_factory( + entitled=True, + extra_args={ + "applicability_status": (ApplicabilityStatus.APPLICABLE, ""), + "application_status": (ApplicationStatus.DISABLED, ""), + "enable": True, + "required_services": ( + mock.MagicMock(entitlement=m_required_service_cls), + ), + }, + ) - expected_prompt_call = 1 - expected_ret = False - expected_reason = CanEnableFailureReason.INACTIVE_REQUIRED_SERVICES - if assume_yes: - expected_ret = True - expected_reason = None - expected_enable_call = 1 if expected_ret else 0 + ret, reason = entitlement.enable(mock.MagicMock()) - assert ret == expected_ret - if expected_reason is None: - assert reason is None - else: - assert reason.reason == expected_reason - assert m_prompt.call_count == expected_prompt_call - assert m_ent_obj.enable.call_count == expected_enable_call + assert ret is True + assert reason is None + assert m_required_service_obj.enable.call_count == 1 @pytest.mark.parametrize( "can_enable_fail,handle_incompat_calls,enable_req_calls", @@ -554,15 +499,15 @@ can_enable_fail, handle_incompat_calls, enable_req_calls, - concrete_entitlement_factory, + base_entitlement_factory, ): """When can_enable returns False enable returns False.""" m_can_enable.return_value = (False, can_enable_fail) m_handle_incompat.return_value = (False, None) - entitlement = concrete_entitlement_factory(entitled=True) + entitlement = base_entitlement_factory(entitled=True) entitlement._perform_enable = mock.Mock() - assert (False, can_enable_fail) == entitlement.enable() + assert (False, can_enable_fail) == entitlement.enable(mock.MagicMock()) assert 1 == m_can_enable.call_count assert handle_incompat_calls == m_handle_incompat.call_count @@ -580,7 +525,8 @@ m_handle_msg, m_prompt_for_confirmation, enable_fail_message, - concrete_entitlement_factory, + base_entitlement_factory, + mock_entitlement, ): m_handle_msg.return_value = True @@ -597,27 +543,27 @@ CanEnableFailureReason.NOT_ENTITLED, message=msg ) - m_ent_cls = mock.Mock() - type(m_ent_cls).name = mock.PropertyMock(return_value="Test") - m_ent_obj = m_ent_cls.return_value - m_ent_obj.enable.return_value = (False, enable_fail_reason) - m_ent_obj.application_status.return_value = ( - ApplicationStatus.DISABLED, - None, - ) - type(m_ent_obj).title = mock.PropertyMock(return_value="Test") - m_prompt_for_confirmation.return_vale = True - entitlement = concrete_entitlement_factory( - entitled=True, + m_required_service_cls, m_required_service_obj = mock_entitlement( application_status=(ApplicationStatus.DISABLED, ""), - required_services=(m_ent_cls,), + title="Test", + enable=(False, enable_fail_reason), + ) + + entitlement = base_entitlement_factory( + entitled=True, + extra_args={ + "application_status": (ApplicationStatus.DISABLED, ""), + "required_services": ( + mock.MagicMock(entitlement=m_required_service_cls), + ), + }, ) with mock.patch.object(entitlement, "can_enable") as m_can_enable: m_can_enable.return_value = (False, fail_reason) - ret, fail = entitlement.enable() + ret, fail = entitlement.enable(mock.MagicMock()) assert not ret expected_msg = "Cannot enable required service: Test" @@ -626,56 +572,58 @@ assert expected_msg == fail.message.msg assert 1 == m_can_enable.call_count - @pytest.mark.parametrize( - [ - "msg_ops_results", - "can_enable_call_count", - "perform_enable_call_count", - "expected_result", - ], - ( - ([False], 0, 0, (False, None)), - ([True, False], 1, 0, (False, None)), - ([True, True, False], 1, 1, (False, None)), - ([True, True, True], 1, 1, (True, None)), - ), - ) - @mock.patch.object( - ConcreteTestEntitlement, "_perform_enable", return_value=True - ) - @mock.patch( - "uaclient.entitlements.base.UAEntitlement.can_enable", - return_value=(True, None), - ) @mock.patch("uaclient.util.handle_message_operations") - def test_enable_when_messaging_hooks_fail( + def test_enable_fails_when_blocking_service_is_enabled( self, - m_handle_messaging_hooks, - m_can_enable, - m_perform_enable, - msg_ops_results, - can_enable_call_count, - perform_enable_call_count, - expected_result, - concrete_entitlement_factory, - ): - m_handle_messaging_hooks.side_effect = msg_ops_results - entitlement = concrete_entitlement_factory() - assert expected_result == entitlement.enable() - assert can_enable_call_count == m_can_enable.call_count - assert perform_enable_call_count == m_perform_enable.call_count + _m_handle_message_op, + mock_entitlement, + base_entitlement_factory, + ): + expected_msg = messages.E_INCOMPATIBLE_SERVICE_STOPS_ENABLE.format( + service_being_enabled=ConcreteTestEntitlement.title, + incompatible_service="Test", + ) + incompatible_service_cls, _ = mock_entitlement( + application_status=(ApplicationStatus.ENABLED, ""), + title="Test", + disable=( + False, + CanDisableFailure( + CanDisableFailureReason.ACTIVE_DEPENDENT_SERVICES, + message=expected_msg, + ), + ), + ) + incompatible_services_definition = base.EntitlementWithMessage( + incompatible_service_cls, messages.NamedMessage("code", "msg") + ) + entitlement = base_entitlement_factory( + assume_yes=True, + extra_args={ + "blocking_incompatible_services": [ + incompatible_services_definition + ], + "incompatible_services": [incompatible_services_definition], + }, + ) + result, reason = entitlement.enable(mock.MagicMock()) + assert not result + assert expected_msg.msg == reason.message.msg.strip() -class TestUaEntitlementCanDisable: + +class TestEntitlementCanDisable: def test_can_disable_false_on_purge_not_supported( - self, concrete_entitlement_factory + self, base_entitlement_factory ): """When the entitlement doesn't support purge, can_disable is FALSE.""" - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - supports_purge=False, purge=True, - application_status=(ApplicationStatus.ENABLED, ""), + extra_args={ + "supports_purge": False, + "application_status": (ApplicationStatus.ENABLED, ""), + }, ) can_disable, reason = entitlement.can_disable() @@ -688,40 +636,62 @@ ).msg ) - def test_can_disable_false_on_entitlement_inactive( - self, concrete_entitlement_factory + @pytest.mark.parametrize( + "application_status,expected_ret,expected_fail", + ( + ( + (ApplicationStatus.DISABLED, ""), + False, + CanDisableFailure( + CanDisableFailureReason.ALREADY_DISABLED, + messages.ALREADY_DISABLED.format( + title=ConcreteTestEntitlement.title + ), + ), + ), + ( + (ApplicationStatus.ENABLED, ""), + True, + None, + ), + ), + ) + def test_can_disable_given_entitlement_status( + self, + application_status, + expected_ret, + expected_fail, + base_entitlement_factory, ): - """When status is INACTIVE, can_disable returns False.""" - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - application_status=(ApplicationStatus.DISABLED, ""), + extra_args={ + "application_status": application_status, + }, ) ret, fail = entitlement.can_disable() - assert not ret + assert expected_ret is ret - expected_msg = ( - "Test Concrete Entitlement is not currently enabled\n" - "See: sudo pro status" - ) - assert expected_msg == fail.message.msg + if fail: + assert expected_fail.message.msg == fail.message.msg + assert expected_fail.reason == fail.reason + else: + assert expected_fail is None def test_can_disable_false_on_dependent_service( - self, concrete_entitlement_factory + self, base_entitlement_factory, mock_entitlement ): - """When status is INACTIVE, can_disable returns False.""" - m_ent_cls = mock.Mock() - type(m_ent_cls).name = mock.PropertyMock(return_value="test") - m_ent_obj = m_ent_cls.return_value - m_ent_obj.application_status.return_value = ( - ApplicationStatus.ENABLED, - None, + m_required_service_cls, _ = mock_entitlement( + application_status=(ApplicationStatus.ENABLED, None) ) - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - application_status=(ApplicationStatus.ENABLED, ""), - dependent_services=(m_ent_cls,), + extra_args={ + "application_status": (ApplicationStatus.ENABLED, ""), + "dependent_services": (m_required_service_cls,), + }, ) ret, fail = entitlement.can_disable() @@ -731,226 +701,389 @@ @mock.patch("uaclient.entitlements.entitlement_factory") def test_can_disable_when_ignoring_dependent_service( - self, m_ent_factory, concrete_entitlement_factory + self, m_ent_factory, base_entitlement_factory, mock_entitlement ): - """When status is INACTIVE, can_disable returns False.""" - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - application_status=(ApplicationStatus.ENABLED, ""), - dependent_services=("test",), + extra_args={ + "application_status": (ApplicationStatus.ENABLED, ""), + "dependent_services": ("test",), + }, ) - m_ent_cls = mock.Mock() - m_ent_obj = m_ent_cls.return_value - m_ent_obj.application_status.return_value = ( - ApplicationStatus.ENABLED, - None, + m_dependent_service_cls, _ = mock_entitlement( + application_status=(ApplicationStatus.ENABLED, None) ) - m_ent_factory.return_value = m_ent_cls + m_ent_factory.return_value = m_dependent_service_cls ret, fail = entitlement.can_disable(ignore_dependent_services=True) assert ret is True assert fail is None - def test_can_disable_true_on_entitlement_active( - self, capsys, concrete_entitlement_factory - ): - """When entitlement is ENABLED, can_disable returns True.""" - entitlement = concrete_entitlement_factory( - entitled=True, - application_status=(ApplicationStatus.ENABLED, ""), - ) - - assert entitlement.can_disable() - - stdout, _ = capsys.readouterr() - assert "" == stdout - -class TestUaEntitlementDisable: - @mock.patch("uaclient.util.prompt_for_confirmation") +class TestEntitlementDisable: def test_disable_when_dependent_service_found( - self, m_prompt, concrete_entitlement_factory + self, base_entitlement_factory, mock_entitlement ): - m_prompt.return_value = True - - m_ent_cls = mock.MagicMock() - m_ent_obj = m_ent_cls.return_value - m_ent_obj.application_status.return_value = [ - ApplicationStatus.ENABLED, - "", - ] - m_ent_obj.disable.return_value = (True, None) - type(m_ent_obj).title = mock.PropertyMock(return_value="test") - base_ent = concrete_entitlement_factory( - entitled=True, - disable=True, + m_dependent_service_cls, m_dependent_service_obj = mock_entitlement( application_status=(ApplicationStatus.ENABLED, ""), - dependent_services=(m_ent_cls,), + disable=(True, None), + ) + entitlement = base_entitlement_factory( + entitled=True, + extra_args={ + "application_status": (ApplicationStatus.ENABLED, ""), + "disable": True, + "dependent_services": (m_dependent_service_cls,), + }, ) - ret, fail = base_ent.disable() + ret, fail = entitlement.disable(mock.MagicMock()) - expected_prompt_call = 1 expected_ret = True expected_disable_call = 1 assert ret == expected_ret assert fail is None - assert m_prompt.call_count == expected_prompt_call - assert m_ent_obj.disable.call_count == expected_disable_call + assert ( + m_dependent_service_obj.disable.call_count == expected_disable_call + ) @pytest.mark.parametrize("disable_fail_message", (("error"), (None))) @mock.patch("uaclient.util.handle_message_operations") - @mock.patch("uaclient.util.prompt_for_confirmation") def test_disable_false_when_fails_to_disable_dependent_service( self, m_handle_msg, - m_prompt_for_confirmation, disable_fail_message, - concrete_entitlement_factory, + base_entitlement_factory, + mock_entitlement, ): m_handle_msg.return_value = True fail_reason = CanDisableFailure( CanDisableFailureReason.ACTIVE_DEPENDENT_SERVICES ) + + msg = None if disable_fail_message: msg = messages.NamedMessage("test-code", disable_fail_message) - else: - msg = None disable_fail_reason = CanDisableFailure( CanDisableFailureReason.ALREADY_DISABLED, message=msg ) - - m_ent_cls = mock.Mock() - type(m_ent_cls).name = mock.PropertyMock(return_value="Test") - m_ent_obj = m_ent_cls.return_value - m_ent_obj.disable.return_value = (False, disable_fail_reason) - m_ent_obj.application_status.return_value = ( - ApplicationStatus.ENABLED, - None, + m_dependent_service_cls, _ = mock_entitlement( + name="Test", + title="Test", + disable=(False, disable_fail_reason), + application_status=(ApplicationStatus.ENABLED, ""), ) - type(m_ent_obj).title = mock.PropertyMock(return_value="Test") - m_prompt_for_confirmation.return_vale = True - - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - application_status=(ApplicationStatus.DISABLED, ""), - dependent_services=(m_ent_cls,), + extra_args={ + "application_status": (ApplicationStatus.DISABLED, ""), + "dependent_services": (m_dependent_service_cls,), + }, ) with mock.patch.object(entitlement, "can_disable") as m_can_disable: m_can_disable.return_value = (False, fail_reason) - ret, fail = entitlement.disable() + ret, fail = entitlement.disable(mock.MagicMock()) assert not ret - expected_msg = "Cannot disable dependent service: Test" - if disable_fail_reason.message: - expected_msg += "\n" + disable_fail_reason.message.msg + expected_msg = messages.FAILED_DISABLING_DEPENDENT_SERVICE.format( + required_service="Test", + error="\n" + msg.msg if msg else "", + ).msg assert expected_msg == fail.message.msg assert 1 == m_can_disable.call_count - -class TestUaEntitlementContractStatus: - def test_contract_status_entitled(self, concrete_entitlement_factory): - """The contract_status returns ENTITLED when entitlement enabled.""" - entitlement = concrete_entitlement_factory(entitled=True) - assert ContractStatus.ENTITLED == entitlement.contract_status() - - def test_contract_status_unentitled(self, concrete_entitlement_factory): - """The contract_status returns NONE when entitlement is unentitled.""" - entitlement = concrete_entitlement_factory(entitled=False) - assert ContractStatus.UNENTITLED == entitlement.contract_status() - - -class TestUaEntitlementUserFacingStatus: - def test_inapplicable_when_not_applicable( - self, concrete_entitlement_factory + def test_disable_returns_false_on_can_disable_false_and_does_nothing( + self, + base_entitlement_factory, ): - msg = "inapplicable for very good reasons" - entitlement = concrete_entitlement_factory( - entitled=True, - applicability_status=( - ApplicabilityStatus.INAPPLICABLE, - msg, - ), - ) + """When can_disable is false disable returns false and noops.""" + entitlement = base_entitlement_factory() - user_facing_status, details = entitlement.user_facing_status() - assert UserFacingStatus.INAPPLICABLE == user_facing_status - assert msg == details + with mock.patch.object( + entitlement, "can_disable", return_value=(False, None) + ) as m_can_disable: + ret, fail = entitlement.disable(mock.MagicMock()) - def test_unavailable_when_applicable_but_not_entitled( - self, concrete_entitlement_factory + assert ret is False + assert fail is None + assert [mock.call()] == m_can_disable.call_args_list + + +class TestEntitlementContractStatus: + @pytest.mark.parametrize( + "entitled,expected_contract_status", + ( + ( + True, + ContractStatus.ENTITLED, + ), + ( + False, + ContractStatus.UNENTITLED, + ), + ), + ) + def test_contract_status( + self, entitled, expected_contract_status, base_entitlement_factory ): - entitlement = concrete_entitlement_factory( - entitled=False, - applicability_status=(ApplicabilityStatus.APPLICABLE, ""), + entitlement = base_entitlement_factory(entitled=entitled) + assert expected_contract_status == entitlement.contract_status() + + +class TestUserFacingStatus: + @pytest.mark.parametrize( + "entitled,applicability_status,application_status,expected_status,expected_details", # noqa + ( + ( + True, + ( + ApplicabilityStatus.INAPPLICABLE, + None, + ), + ( + ApplicationStatus.DISABLED, + None, + ), + UserFacingStatus.INAPPLICABLE, + None, + ), + ( + False, + ( + ApplicabilityStatus.APPLICABLE, + "", + ), + ( + ApplicationStatus.DISABLED, + None, + ), + UserFacingStatus.UNAVAILABLE, + messages.SERVICE_NOT_ENTITLED.format( + title=ConcreteTestEntitlement.title + ), + ), + ( + True, + ( + ApplicabilityStatus.APPLICABLE, + "", + ), + ( + ApplicationStatus.DISABLED, + None, + ), + UserFacingStatus.INACTIVE, + None, + ), + ( + True, + ( + ApplicabilityStatus.APPLICABLE, + "", + ), + ( + ApplicationStatus.WARNING, + None, + ), + UserFacingStatus.WARNING, + None, + ), + ), + ) + def test_user_facing_status( + self, + entitled, + applicability_status, + application_status, + expected_status, + expected_details, + base_entitlement_factory, + ): + entitlement = base_entitlement_factory( + entitled=entitled, + extra_args={ + "applicability_status": applicability_status, + "application_status": application_status, + }, ) user_facing_status, details = entitlement.user_facing_status() - assert UserFacingStatus.UNAVAILABLE == user_facing_status - expected_details = "{} is not entitled".format(entitlement.title) - assert expected_details == details.msg + assert expected_status == user_facing_status + + if details: + assert expected_details.msg == details.msg + else: + assert expected_details is None def test_unavailable_when_applicable_but_no_entitlement_cfg( - self, concrete_entitlement_factory + self, base_entitlement_factory ): - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=False, - applicability_status=(ApplicabilityStatus.APPLICABLE, ""), + extra_args={ + "applicability_status": (ApplicabilityStatus.APPLICABLE, "") + }, ) - entitlement.cfg._entitlements = {} + with mock.patch.object( + entitlement, "_base_entitlement_cfg", return_value={} + ): + user_facing_status, details = entitlement.user_facing_status() - user_facing_status, details = entitlement.user_facing_status() assert UserFacingStatus.UNAVAILABLE == user_facing_status - expected_details = "{} is not entitled".format(entitlement.title) + expected_details = messages.SERVICE_NOT_ENTITLED.format( + title=entitlement.title + ).msg assert expected_details == details.msg + +class TestApplicabilityStatus: @pytest.mark.parametrize( - "application_status,expected_uf_status", ( - (ApplicationStatus.ENABLED, UserFacingStatus.ACTIVE), + "arch,series,version,min_kernel,kernel_flavors," + "expected_status,expected_message" + ), + ( ( - ApplicationStatus.DISABLED, - UserFacingStatus.INACTIVE, + "arm64", + "xenial", + "16.04 LTS (Xenial Xerus)", + "4.4", + ["generic", "lowlatency"], + ApplicabilityStatus.INAPPLICABLE, + messages.INAPPLICABLE_ARCH.format( + title=ConcreteTestEntitlement.title, + arch="arm64", + supported_arches=", ".join(["amd64", "s390x"]), + ), + ), + ( + "s390x", + "bionic", + "18.04 LTS (Bionic Beaver)", + "4.4", + ["generic", "lowlatency"], + ApplicabilityStatus.INAPPLICABLE, + messages.INAPPLICABLE_SERIES.format( + title=ConcreteTestEntitlement.title, + series="18.04 LTS (Bionic Beaver)", + ), + ), + ( + "s390x", + "xenial", + "16.04 LTS (Xenial Xerus)", + "5.0", + ["generic", "lowlatency"], + ApplicabilityStatus.INAPPLICABLE, + messages.INAPPLICABLE_KERNEL_VER.format( + title=ConcreteTestEntitlement.title, + kernel="4.19.0-00-generic", + min_kernel="5.0", + ), + ), + ( + "s390x", + "xenial", + "16.04 LTS (Xenial Xerus)", + "5.0", + ["lowlatency"], + ApplicabilityStatus.INAPPLICABLE, + messages.INAPPLICABLE_KERNEL.format( + title=ConcreteTestEntitlement.title, + kernel="4.19.0-00-generic", + supported_kernels="lowlatency", + ), + ), + ( + "s390x", + "xenial", + "16.04 LTS (Xenial Xerus)", + "4.4", + ["generic", "lowlatency"], + ApplicabilityStatus.APPLICABLE, + None, ), ), ) - def test_application_status_used_if_not_inapplicable( + @mock.patch("uaclient.system.get_kernel_info") + @mock.patch("uaclient.system.get_dpkg_arch") + @mock.patch("uaclient.system.get_release_info") + def test_applicability_status( self, - concrete_entitlement_factory, - application_status, - expected_uf_status, - ): - msg = "application status details" - entitlement = concrete_entitlement_factory( - entitled=True, - applicability_status=(ApplicabilityStatus.APPLICABLE, ""), - application_status=(application_status, msg), + m_release_info, + m_dpkg_arch, + m_get_kernel_info, + arch, + series, + version, + min_kernel, + kernel_flavors, + expected_status, + expected_message, + base_entitlement_factory, + ): + m_release_info.return_value = system.ReleaseInfo( + distribution="", release="", series=series, pretty_version=version + ) + m_get_kernel_info.return_value = system.KernelInfo( + uname_machine_arch="", + uname_release="4.19.0-00-generic", + proc_version_signature_version="", + build_date=None, + major=4, + minor=19, + patch=0, + abi="00", + flavor="generic", + ) + + m_dpkg_arch.return_value = arch + + entitlement = base_entitlement_factory( + directives={ + "aptURL": "http://CC", + "aptKey": "APTKEY", + "suites": ["xenial"], + "additionalPackages": ["test-package"], + }, + affordances={ + "architectures": ["x86_64", "s390x"], + "series": ["xenial"], + "minKernelVersion": min_kernel, + "kernelFlavors": kernel_flavors, + }, ) - user_facing_status, details = entitlement.user_facing_status() - assert expected_uf_status == user_facing_status - assert msg == details + actual_status, actual_message = entitlement.applicability_status() + assert expected_status == actual_status + + if expected_message: + assert expected_message.msg == actual_message.msg + else: + assert actual_message is None -class TestUaEntitlementProcessContractDeltas: +class TestEntitlementProcessContractDeltas: @pytest.mark.parametrize( "orig_access,delta", (({}, {}), ({}, {"entitlement": {"entitled": False}})), ) def test_process_contract_deltas_does_nothing_on_empty_orig_access( - self, concrete_entitlement_factory, orig_access, delta + self, base_entitlement_factory, orig_access, delta ): """When orig_acccess dict is empty perform no work.""" - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - applicability_status=(ApplicabilityStatus.APPLICABLE, ""), - application_status=(ApplicationStatus.DISABLED, ""), + extra_args={ + "applicability_status": (ApplicabilityStatus.APPLICABLE, ""), + "application_status": (ApplicationStatus.DISABLED, ""), + }, ) with mock.patch.object(entitlement, "can_disable") as m_can_disable: entitlement.process_contract_deltas(orig_access, delta) @@ -982,13 +1115,15 @@ ), ) def test_process_contract_deltas_does_nothing_when_delta_remains_entitled( - self, m_platform_info, concrete_entitlement_factory, orig_access, delta + self, m_platform_info, base_entitlement_factory, orig_access, delta ): """If deltas do not represent transition to unentitled, do nothing.""" - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - applicability_status=(ApplicabilityStatus.APPLICABLE, ""), - application_status=(ApplicationStatus.DISABLED, ""), + extra_args={ + "applicability_status": (ApplicabilityStatus.APPLICABLE, ""), + "application_status": (ApplicationStatus.DISABLED, ""), + }, ) entitlement.process_contract_deltas(orig_access, delta) assert ( @@ -1012,12 +1147,14 @@ ), ) def test_process_contract_deltas_clean_cache_on_inactive_unentitled( - self, concrete_entitlement_factory, orig_access, delta, caplog_text + self, base_entitlement_factory, orig_access, delta, caplog_text ): """Only clear cache when deltas transition inactive to unentitled.""" - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - application_status=(ApplicationStatus.DISABLED, ""), + extra_args={ + "application_status": (ApplicationStatus.DISABLED, ""), + }, ) entitlement.process_contract_deltas(orig_access, delta) # If an entitlement is disabled, we don't need to tell the user @@ -1044,14 +1181,21 @@ ), ) def test_process_contract_deltas_disable_on_active_unentitled( - self, concrete_entitlement_factory, orig_access, delta + self, base_entitlement_factory, orig_access, delta ): """Disable when deltas transition from active to unentitled.""" - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - application_status=(ApplicationStatus.ENABLED, ""), + extra_args={ + "application_status": (ApplicationStatus.ENABLED, ""), + }, ) - entitlement.process_contract_deltas(orig_access, delta) + + with mock.patch.object( + entitlement, "can_disable", return_value=(True, None) + ): + entitlement.process_contract_deltas(orig_access, delta) + assert ( ApplicationStatus.DISABLED, mock.ANY, @@ -1078,21 +1222,26 @@ ), ) def test_process_contract_deltas_enable_beta_if_enabled_by_default_turned( - self, concrete_entitlement_factory, orig_access, delta + self, base_entitlement_factory, orig_access, delta ): """Disable when deltas transition from active to unentitled.""" - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - applicability_status=(ApplicabilityStatus.APPLICABLE, ""), - application_status=(ApplicationStatus.DISABLED, ""), + extra_args={ + "applicability_status": (ApplicabilityStatus.APPLICABLE, ""), + "application_status": (ApplicationStatus.DISABLED, ""), + }, ) entitlement.is_beta = True assert not entitlement.allow_beta - with mock.patch.object(entitlement, "enable") as m_enable: - entitlement.process_contract_deltas( - orig_access, delta, allow_enable=True - ) - assert 1 == m_enable.call_count + with mock.patch.object( + entitlement, "can_enable", return_value=(True, None) + ): + with mock.patch.object(entitlement, "enable") as m_enable: + entitlement.process_contract_deltas( + orig_access, delta, allow_enable=True + ) + assert 1 == m_enable.call_count assert entitlement.allow_beta @@ -1102,62 +1251,53 @@ "variant_name", ((""), ("test-variant"), ("invalid-variant")) ) def test_entitlement_cfg_respects_variant( - self, variant_name, concrete_entitlement_factory + self, variant_name, base_entitlement_factory ): - entitlement = concrete_entitlement_factory( + entitlement = base_entitlement_factory( entitled=True, - applicability_status=(ApplicabilityStatus.APPLICABLE, ""), - application_status=(ApplicationStatus.DISABLED, ""), - variant_name=variant_name, - ) - base_ent_dict = { - "entitlement": { - "entitled": True, - "obligations": {"enableByDefault": False}, - "affordances": { - "architectures": [ - "amd64", - "ppc64el", - ], - "series": ["xenial", "bionic", "focal"], - }, - "directives": { - "additionalPackages": ["test-package"], - "suites": ["xenial", "bionic", "focal"], - }, - "overrides": [ - { - "directives": { - "additionalPackages": ["test-package-variant"] - }, - "selector": { - "variant": "test-variant", - }, + obligations={"enableByDefault": False}, + affordances={ + "architectures": [ + "amd64", + "ppc64el", + ], + "series": ["xenial", "bionic", "focal"], + }, + directives={ + "additionalPackages": ["test-package"], + "suites": ["xenial", "bionic", "focal"], + }, + overrides=[ + { + "directives": { + "additionalPackages": ["test-package-variant"] }, - { - "directives": { - "additionalPackages": ["test-package-unused"] - }, - "selector": {"cloud": "aws", "series": "focal"}, + "selector": { + "variant": "test-variant", }, - ], - "type": "test", - } - } - - expected_entitlement = copy.deepcopy(base_ent_dict) + }, + { + "directives": { + "additionalPackages": ["test-package-unused"] + }, + "selector": {"cloud": "aws", "series": "focal"}, + }, + ], + extra_args={ + "applicability_status": (ApplicabilityStatus.APPLICABLE, ""), + "application_status": (ApplicationStatus.DISABLED, ""), + "variant_name": variant_name, + }, + ) + expected_entitlement = copy.deepcopy( + entitlement._base_entitlement_cfg() + ) if variant_name == "test-variant": expected_entitlement["entitlement"]["directives"][ "additionalPackages" ] = ["test-package-variant"] - with mock.patch.object( - entitlement, "_base_entitlement_cfg" - ) as m_ent_cfg: - m_ent_cfg.return_value = base_ent_dict - actual_entitlement = entitlement.entitlement_cfg - - assert expected_entitlement == actual_entitlement + assert expected_entitlement == entitlement.entitlement_cfg class TestVariant: @@ -1179,9 +1319,9 @@ m_get_contract_variants, m_get_variants, contract_variants, - concrete_entitlement_factory, + base_entitlement_factory, ): - entitlement = concrete_entitlement_factory() + entitlement = base_entitlement_factory() service_variants = {"test_variant": "test", "generic": "generic"} m_get_contract_variants.return_value = contract_variants m_get_variants.return_value = service_variants @@ -1197,79 +1337,60 @@ class TestGetContractVariant: @pytest.mark.parametrize( - "entitlement_cfg", + "overrides", ( - ({}), + ([]), ( - { - "entitlement": { - "overrides": [ - { - "selector": { - "variant": "test1", - }, - }, - { - "selector": { - "variant": "test2", - } - }, - { - "selector": { - "cloud": "cloud", - }, - }, - ], - } - } + [ + { + "selector": { + "variant": "test1", + }, + }, + { + "selector": { + "variant": "test2", + } + }, + { + "selector": { + "cloud": "cloud", + }, + }, + ] ), ), ) - @mock.patch( - "uaclient.entitlements.base.UAEntitlement._base_entitlement_cfg" - ) - def test_get_contract_variant( - self, m_base_ent_cfg, entitlement_cfg, concrete_entitlement_factory - ): - entitlement = concrete_entitlement_factory() - m_base_ent_cfg.return_value = entitlement_cfg - + def test_get_contract_variant(self, overrides, base_entitlement_factory): + entitlement = base_entitlement_factory(overrides=overrides) actual_contract_variants = entitlement._get_contract_variants() expected_contract_variants = ( - set() if not entitlement_cfg else set(["test1", "test2"]) + set() if not overrides else set(["test1", "test2"]) ) assert expected_contract_variants == actual_contract_variants - assert 1 == m_base_ent_cfg.call_count class TestHandleRequiredSnaps: @pytest.mark.parametrize( - "entitlement_cfg", + "directives", ( ({}), ( { - "entitlement": { - "directives": { - "requiredSnaps": [ - {"name": "test1", "channel": "latest/stable"}, - { - "name": "test2", - "classicConfinementSupport": True, - }, - {"name": "test3"}, - ] + "requiredSnaps": [ + {"name": "test1", "channel": "latest/stable"}, + { + "name": "test2", + "classicConfinementSupport": True, }, - } + {"name": "test3"}, + ] } ), ), ) @mock.patch( - "uaclient.entitlements.base.UAEntitlement._base_entitlement_cfg" - ) - @mock.patch( "uaclient.snap.is_snapd_installed_as_a_snap", return_value=True ) @mock.patch("uaclient.snap.is_snapd_installed", return_value=True) @@ -1287,21 +1408,19 @@ m_run_snapd_wait_cmd, m_is_snapd_installed, m_is_snapd_installed_as_a_snap, - m_base_ent_cfg, - entitlement_cfg, - concrete_entitlement_factory, + directives, + base_entitlement_factory, ): - entitlement = concrete_entitlement_factory() - m_base_ent_cfg.return_value = entitlement_cfg + entitlement = base_entitlement_factory(directives=directives) m_get_snap_info.side_effect = [ exceptions.SnapNotInstalledError(snap="snap"), exceptions.SnapNotInstalledError(snap="snap"), mock.MagicMock, ] - assert entitlement.handle_required_snaps() + assert entitlement.handle_required_snaps(mock.MagicMock()) - if not entitlement_cfg: + if not directives: assert 0 == m_is_snapd_installed.call_count assert 0 == m_is_snapd_installed_as_a_snap.call_count assert 0 == m_run_snapd_wait_cmd.call_count @@ -1348,45 +1467,40 @@ ), ( [{"name": "package"}], - [mock.call()], + [mock.call("/etc/apt/sources.list")], [mock.call(["package"])], True, ), ( [{"name": "package"}, {"name": "package2"}], - [mock.call()], + [mock.call("/etc/apt/sources.list")], [mock.call(["package", "package2"])], True, ), ], ) + @mock.patch("uaclient.apt.update_sources_list") @mock.patch("uaclient.apt.run_apt_install_command") - @mock.patch( - "uaclient.entitlements.base.UAEntitlement._update_sources_list" - ) - @mock.patch( - "uaclient.entitlements.base.UAEntitlement.entitlement_cfg", - new_callable=mock.PropertyMock, - ) + @mock.patch("uaclient.apt.run_apt_update_command") def test_handle_required_packages( self, - m_entitlement_cfg, - m_update_sources_list, + m_apt_update, m_apt_install, + m_update_sources_list, required_packages_directive, expected_apt_update_calls, expected_apt_install_calls, expected_result, - concrete_entitlement_factory, + base_entitlement_factory, ): - entitlement = concrete_entitlement_factory() - m_entitlement_cfg.return_value = { - "entitlement": { - "directives": {"requiredPackages": required_packages_directive} - } - } + entitlement = base_entitlement_factory( + directives={"requiredPackages": required_packages_directive}, + ) - assert expected_result == entitlement.handle_required_packages() + assert expected_result == entitlement.handle_required_packages( + mock.MagicMock() + ) + assert [] == m_apt_update.call_args_list assert ( expected_apt_update_calls == m_update_sources_list.call_args_list ) @@ -1440,25 +1554,17 @@ ], ) @mock.patch("uaclient.apt.remove_packages") - @mock.patch( - "uaclient.entitlements.base.UAEntitlement.entitlement_cfg", - new_callable=mock.PropertyMock, - ) def test_handle_removing_required_packages( self, - m_entitlement_cfg, m_apt_remove, required_packages_directive, expected_apt_remove_calls, expected_result, - concrete_entitlement_factory, + base_entitlement_factory, ): - entitlement = concrete_entitlement_factory() - m_entitlement_cfg.return_value = { - "entitlement": { - "directives": {"requiredPackages": required_packages_directive} - } - } + entitlement = base_entitlement_factory( + directives={"requiredPackages": required_packages_directive}, + ) assert ( expected_result == entitlement.handle_removing_required_packages() diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_cc.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_cc.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_cc.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_cc.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,221 +1,41 @@ -"""Tests related to uaclient.entitlement.base module.""" +from functools import partial -import itertools -import os.path - -import mock import pytest -from uaclient import apt, messages, status, system +from uaclient import messages from uaclient.entitlements.cc import CC_README, CommonCriteriaEntitlement -from uaclient.entitlements.tests.conftest import machine_token -M_REPOPATH = "uaclient.entitlements.repo." -CC_MACHINE_TOKEN = machine_token( - entitlement_type="cc-eal", - obligations={"enableByDefault": False}, - entitled=True, - directives={ - "aptURL": "http://CC", - "aptKey": "APTKEY", - "suites": ["xenial"], - "additionalPackages": ["ubuntu-commoncriteria"], - }, - affordances={ - "architectures": ["x86_64", "ppc64le", "s390x"], - "series": ["xenial"], - }, -) +@pytest.fixture +def cc_entitlement(entitlement_factory): + return partial( + entitlement_factory, + CommonCriteriaEntitlement, + ) -class TestCommonCriteriaEntitlementUserFacingStatus: +class TestCISEntitlement: @pytest.mark.parametrize( - "arch,series,version,details", + "access_only,expected", ( ( - "arm64", - "xenial", - "16.04 LTS (Xenial Xerus)", - "CC EAL2 is not available for platform arm64.\n" - "Supported platforms are: amd64, ppc64el, s390x.", + True, + { + "pre_install": [messages.CC_PRE_INSTALL], + "post_enable": None, + }, ), ( - "s390x", - "bionic", - "18.04 LTS (Bionic Beaver)", - "CC EAL2 is not available for Ubuntu 18.04 LTS" - " (Bionic Beaver).", + False, + { + "pre_install": [messages.CC_PRE_INSTALL], + "post_enable": [ + messages.CC_POST_ENABLE.format(filename=CC_README) + ], + }, ), ), ) - @mock.patch("uaclient.system.get_dpkg_arch") - @mock.patch("uaclient.system.get_release_info") - def test_inapplicable_on_invalid_affordances( - self, - m_release_info, - m_dpkg_arch, - arch, - series, - version, - details, - FakeConfig, - ): - """Test invalid affordances result in inapplicable status.""" - m_release_info.return_value = system.ReleaseInfo( - distribution="", release="", series=series, pretty_version=version - ) - m_dpkg_arch.return_value = arch - cfg = FakeConfig().for_attached_machine( - machine_token=CC_MACHINE_TOKEN, - ) - entitlement = CommonCriteriaEntitlement(cfg) - uf_status, uf_status_details = entitlement.user_facing_status() - assert status.UserFacingStatus.INAPPLICABLE == uf_status - assert details == uf_status_details.msg - - -class TestCommonCriteriaEntitlementEnable: - # Paramterize True/False for apt_transport_https and ca_certificates - @pytest.mark.parametrize( - "apt_transport_https,ca_certificates", - itertools.product([False, True], repeat=2), - ) - @mock.patch("uaclient.system.get_kernel_info") - @mock.patch("uaclient.apt.update_sources_list") - @mock.patch("uaclient.apt.setup_apt_proxy") - @mock.patch("uaclient.system.should_reboot") - @mock.patch("uaclient.system.subp") - @mock.patch("uaclient.apt.get_apt_cache_policy") - @mock.patch("uaclient.system.get_dpkg_arch") - @mock.patch("uaclient.system.get_release_info") - @mock.patch("uaclient.contract.apply_contract_overrides") - def test_enable_configures_apt_sources_and_auth_files( - self, - _m_contract_overrides, - m_release_info, - m_dpkg_arch, - m_apt_cache_policy, - m_subp, - m_should_reboot, - m_setup_apt_proxy, - m_update_sources_list, - m_get_kernel_info, - capsys, - event, - apt_transport_https, - ca_certificates, - tmpdir, - FakeConfig, - ): - """When entitled, configure apt repo auth token, pinning and url.""" - m_subp.return_value = ("fakeout", "") - m_apt_cache_policy.return_value = "fakeout" - m_should_reboot.return_value = False - m_release_info.return_value = system.ReleaseInfo( - distribution="", release="", series="xenial", pretty_version="" - ) - m_dpkg_arch.return_value = "s390x" - original_exists = os.path.exists - - def exists(path): - if path == apt.APT_METHOD_HTTPS_FILE: - return not apt_transport_https - elif path == apt.CA_CERTIFICATES_FILE: - return not ca_certificates - elif not path.startswith(tmpdir.strpath): - raise Exception( - "os.path.exists call outside of tmpdir: {}".format(path) - ) - return original_exists(path) - - cfg = FakeConfig().for_attached_machine( - machine_token=CC_MACHINE_TOKEN, - ) - entitlement = CommonCriteriaEntitlement(cfg, allow_beta=True) - - with mock.patch("uaclient.apt.add_auth_apt_repo") as m_add_apt: - with mock.patch("uaclient.apt.add_ppa_pinning") as m_add_pin: - with mock.patch(M_REPOPATH + "exists", side_effect=exists): - assert (True, None) == entitlement.enable() - - add_apt_calls = [ - mock.call( - "/etc/apt/sources.list.d/ubuntu-cc-eal.list", - "http://CC/ubuntu", - "{}-token".format(entitlement.name), - ["xenial"], - entitlement.repo_key_file, - ) - ] - - apt_cache_policy_cmds = [ - mock.call( - error_msg=messages.APT_POLICY_FAILED, - ) - ] - - prerequisite_pkgs = [] - if apt_transport_https: - prerequisite_pkgs.append("apt-transport-https") - if ca_certificates: - prerequisite_pkgs.append("ca-certificates") - - subp_apt_cmds = [] - if prerequisite_pkgs: - expected_stdout = "Installing {}\n".format( - ", ".join(prerequisite_pkgs) - ) - subp_apt_cmds.append( - mock.call( - ["apt-get", "install", "--assume-yes"] + prerequisite_pkgs, - capture=True, - retry_sleeps=apt.APT_RETRIES, - override_env_vars=None, - ) - ) - else: - expected_stdout = "" - - subp_apt_cmds.extend( - [ - mock.call( - [ - "apt-get", - "install", - "--assume-yes", - "--allow-downgrades", - '-o Dpkg::Options::="--force-confdef"', - '-o Dpkg::Options::="--force-confold"', - ] - + entitlement.packages, - capture=True, - retry_sleeps=apt.APT_RETRIES, - override_env_vars={"DEBIAN_FRONTEND": "noninteractive"}, - ), - ] - ) - - assert add_apt_calls == m_add_apt.call_args_list - # No apt pinning for cc - assert [] == m_add_pin.call_args_list - assert 1 == m_setup_apt_proxy.call_count - assert 1 == m_should_reboot.call_count - assert 1 == m_apt_cache_policy.call_count - assert apt_cache_policy_cmds == m_apt_cache_policy.call_args_list - assert subp_apt_cmds == m_subp.call_args_list - assert 2 == m_update_sources_list.call_count - expected_stdout += "\n".join( - [ - "Updating CC EAL2 package lists", - "(This will download more than 500MB of packages, so may take" - " some time.)", - "Updating standard Ubuntu package lists", - "Installing CC EAL2 packages", - "CC EAL2 enabled", - "Please follow instructions in {} to configure EAL2\n".format( - CC_README - ), - ] - ) - assert (expected_stdout, "") == capsys.readouterr() + def test_messages(self, access_only, expected, cc_entitlement): + entitlement = cc_entitlement(access_only=access_only) + assert expected == entitlement.messaging diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_cis.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_cis.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_cis.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_cis.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,125 +1,93 @@ """Tests related to uaclient.entitlement.base module.""" -import mock +from functools import partial + import pytest -from uaclient import apt, messages, system +from uaclient import messages from uaclient.entitlements.cis import CISEntitlement -M_REPOPATH = "uaclient.entitlements.repo." - @pytest.fixture -def entitlement(entitlement_factory): - return entitlement_factory( +def cis_entitlement(entitlement_factory): + return partial( + entitlement_factory, CISEntitlement, - allow_beta=True, - called_name="cis", - additional_packages=["pkg1"], ) -class TestCISEntitlementEnable: - @mock.patch("uaclient.apt.get_apt_cache_policy") - @mock.patch("uaclient.apt.update_sources_list") - @mock.patch("uaclient.apt.setup_apt_proxy") - @mock.patch("uaclient.system.should_reboot") - @mock.patch("uaclient.system.subp") - @mock.patch("uaclient.system.get_kernel_info") - @mock.patch("uaclient.system.get_release_info") - def test_enable_configures_apt_sources_and_auth_files( - self, - m_release_info, - m_kernel_info, - m_subp, - m_should_reboot, - m_setup_apt_proxy, - m_update_sources_list, - m_apt_policy, - capsys, - event, - entitlement, +class TestCISEntitlement: + @pytest.mark.parametrize( + "called_name,presentation_name,expected", + ( + ( + "cis", + "cis", + { + "post_enable": [messages.CIS_POST_ENABLE], + }, + ), + ( + "cis", + "usg", + { + "pre_can_enable": [messages.CIS_IS_NOW_USG], + "post_enable": [messages.CIS_POST_ENABLE], + }, + ), + ( + "usg", + "cis", + { + "post_enable": [messages.CIS_USG_POST_ENABLE], + }, + ), + ( + "usg", + "usg", + { + "post_enable": [messages.CIS_USG_POST_ENABLE], + }, + ), + ), + ) + def test_messages( + self, called_name, presentation_name, expected, cis_entitlement ): - """When entitled, configure apt repo auth token, pinning and url.""" + entitlement = cis_entitlement( + called_name=called_name, + affordances={"presentedAs": presentation_name}, + ) - def fake_platform(key=None): - info = {"series": "xenial", "kernel": "4.15.0-00-generic"} - if key: - return info[key] - return info + assert expected == entitlement.messaging - m_release_info.return_value = system.ReleaseInfo( - distribution="", release="", series="xenial", pretty_version="" - ) - m_kernel_info.return_value = system.KernelInfo( - uname_machine_arch="x86_64", - uname_release="4.15.0-00-generic", - proc_version_signature_version=None, - build_date=None, - major=None, - minor=None, - patch=None, - abi=None, - flavor=None, + @pytest.mark.parametrize( + "called_name,expected", + ( + ("cis", ["test-package"]), + ("usg", []), + ), + ) + def test_packages(self, called_name, expected, cis_entitlement): + entitlement = cis_entitlement( + called_name=called_name, + directives={ + "additionalPackages": ["test-package"], + }, ) - m_subp.return_value = ("fakeout", "") - m_apt_policy.return_value = "fakeout" - m_should_reboot.return_value = False - - with mock.patch(M_REPOPATH + "exists", mock.Mock(return_value=True)): - with mock.patch("uaclient.apt.add_auth_apt_repo") as m_add_apt: - with mock.patch("uaclient.apt.add_ppa_pinning") as m_add_pin: - assert entitlement.enable() - - add_apt_calls = [ - mock.call( - "/etc/apt/sources.list.d/ubuntu-cis.list", - "http://CIS/ubuntu", - "{}-token".format(entitlement.name), - ["xenial"], - entitlement.repo_key_file, - ) - ] - - m_apt_policy_cmds = [ - mock.call( - error_msg=messages.APT_POLICY_FAILED, - ), - ] - subp_apt_cmds = [ - mock.call( - [ - "apt-get", - "install", - "--assume-yes", - "--allow-downgrades", - '-o Dpkg::Options::="--force-confdef"', - '-o Dpkg::Options::="--force-confold"', - ] - + entitlement.packages, - capture=True, - retry_sleeps=apt.APT_RETRIES, - override_env_vars={"DEBIAN_FRONTEND": "noninteractive"}, - ), - ] + assert expected == entitlement.packages - assert add_apt_calls == m_add_apt.call_args_list - # No apt pinning for cis - assert [] == m_add_pin.call_args_list - assert 1 == m_setup_apt_proxy.call_count - assert subp_apt_cmds == m_subp.call_args_list - assert 2 == m_update_sources_list.call_count - assert 1 == m_apt_policy.call_count - assert m_apt_policy_cmds == m_apt_policy.call_args_list - assert 1 == m_should_reboot.call_count - expected_stdout = ( - "Updating CIS Audit package lists\n" - "Updating standard Ubuntu package lists\n" - "Installing CIS Audit packages\n" - "CIS Audit enabled\n" - "Visit {} to learn how to use CIS\n".format( - messages.urls.CIS_HOME_PAGE - ) + @pytest.mark.parametrize( + "called_name,expected", + ( + ("cis", messages.CIS_TITLE), + ("usg", messages.CIS_USG_TITLE), + ), + ) + def test_title(self, called_name, expected, cis_entitlement): + entitlement = cis_entitlement( + called_name=called_name, ) - assert (expected_stdout, "") == capsys.readouterr() + + assert expected == entitlement.title diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_entitlement_status.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_entitlement_status.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_entitlement_status.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_entitlement_status.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,31 @@ +import pytest + +from uaclient import messages +from uaclient.entitlements.entitlement_status import ( + CanDisableFailure, + CanDisableFailureReason, +) + + +class TestCanDisableFailure: + @pytest.mark.parametrize( + "can_disable_failure,expected", + ( + ( + CanDisableFailure( + reason=CanDisableFailureReason.ALREADY_DISABLED, + message=None, + ), + "", + ), + ( + CanDisableFailure( + reason=CanDisableFailureReason.NOT_APPLICABLE, + message=messages.NamedMessage("test", "test"), + ), + "test", + ), + ), + ) + def test_message_value(self, can_disable_failure, expected): + assert expected == can_disable_failure.message_value diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_entitlements.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_entitlements.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_entitlements.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_entitlements.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,8 +1,10 @@ """Tests related to uaclient.entitlement.__init__ module.""" + import mock import pytest from uaclient import entitlements, exceptions, messages +from uaclient.entitlements.entitlement_status import ApplicabilityStatus class TestValidServices: @@ -154,14 +156,17 @@ m_cls_1 = mock.MagicMock() m_obj_1 = m_cls_1.return_value type(m_obj_1).required_services = mock.PropertyMock( - return_value=(m_cls_2,) + return_value=(mock.MagicMock(entitlement=m_cls_2),) ) type(m_cls_1).name = mock.PropertyMock(return_value="ent1") m_cls_3 = mock.MagicMock() m_obj_3 = m_cls_3.return_value type(m_obj_3).required_services = mock.PropertyMock( - return_value=(m_cls_1, m_cls_2) + return_value=( + mock.MagicMock(entitlement=m_cls_1), + mock.MagicMock(entitlement=m_cls_2), + ) ) type(m_cls_3).name = mock.PropertyMock(return_value="ent3") @@ -178,7 +183,10 @@ m_cls_4 = mock.MagicMock() m_obj_4 = m_cls_4.return_value type(m_obj_4).required_services = mock.PropertyMock( - return_value=(m_cls_5, m_cls_6) + return_value=( + mock.MagicMock(entitlement=m_cls_5), + mock.MagicMock(entitlement=m_cls_6), + ) ) type(m_cls_4).name = mock.PropertyMock(return_value="ent4") @@ -212,14 +220,17 @@ m_cls_1 = mock.MagicMock() m_obj_1 = m_cls_1.return_value type(m_obj_1).required_services = mock.PropertyMock( - return_value=(m_cls_2,) + return_value=(mock.MagicMock(entitlement=m_cls_2),) ) type(m_cls_1).name = mock.PropertyMock(return_value="ent1") m_cls_3 = mock.MagicMock() m_obj_3 = m_cls_3.return_value type(m_obj_3).required_services = mock.PropertyMock( - return_value=(m_cls_1, m_cls_2) + return_value=( + mock.MagicMock(entitlement=m_cls_1), + mock.MagicMock(entitlement=m_cls_2), + ) ) type(m_cls_3).name = mock.PropertyMock(return_value="ent3") @@ -236,7 +247,10 @@ m_cls_4 = mock.MagicMock() m_obj_4 = m_cls_4.return_value type(m_obj_4).required_services = mock.PropertyMock( - return_value=(m_cls_5, m_cls_6) + return_value=( + mock.MagicMock(entitlement=m_cls_5), + mock.MagicMock(entitlement=m_cls_6), + ) ) type(m_cls_4).name = mock.PropertyMock(return_value="ent4") @@ -262,3 +276,113 @@ cfg=FakeConfig(), ents=["ent4", "notthere", "ent2", "ent6typo", "ent5"], ) + + +class TestCheckEntitlementAPTDefinitionsAreUnique: + @pytest.mark.parametrize( + ( + "applicability_status1,applicability_status2," + "apt_url1,suite1,apt_url2,suite2,expected" + ), + ( + ( + (ApplicabilityStatus.APPLICABLE, None), + (ApplicabilityStatus.APPLICABLE, None), + "test", + ("release",), + "test", + ("release",), + exceptions.EntitlementsAPTDirectivesAreNotUnique( + url="test_url", + names="ent1, ent2", + apt_url="test", + suite="release", + ), + ), + ( + (ApplicabilityStatus.APPLICABLE, None), + (ApplicabilityStatus.INAPPLICABLE, None), + "test", + ("release",), + "test", + ("release",), + True, + ), + ( + (ApplicabilityStatus.APPLICABLE, None), + (ApplicabilityStatus.APPLICABLE, None), + "test1", + ("release1",), + "test2", + ("release2",), + True, + ), + ( + (ApplicabilityStatus.APPLICABLE, None), + (ApplicabilityStatus.APPLICABLE, None), + "test1", + ("release",), + "test1", + ("release2",), + True, + ), + ( + (ApplicabilityStatus.APPLICABLE, None), + (ApplicabilityStatus.APPLICABLE, None), + "test1", + ("release",), + "test2", + ("release",), + True, + ), + ), + ) + @mock.patch( + "uaclient.entitlements._is_repo_entitlement", return_value=True + ) + @mock.patch("uaclient.entitlements.entitlement_factory") + @mock.patch("uaclient.entitlements.valid_services") + def test_check_entitlement_definitions_are_unique( + self, + m_valid_services, + m_ent_factory, + _m_is_repo_ent, + applicability_status1, + applicability_status2, + apt_url1, + suite1, + apt_url2, + suite2, + expected, + ): + m_valid_services.return_value = ["ent1", "ent2"] + + m_ent1 = mock.MagicMock() + m_ent1_obj = mock.MagicMock() + m_ent1_obj.applicability_status.return_value = applicability_status1 + type(m_ent1_obj).apt_url = apt_url1 + type(m_ent1_obj).apt_suites = suite1 + type(m_ent1_obj).repo_policy_check_tmpl = "{}/ubuntu {}" + m_ent1.return_value = m_ent1_obj + + m_ent2 = mock.MagicMock() + m_ent2_obj = mock.MagicMock() + m_ent2_obj.applicability_status.return_value = applicability_status2 + type(m_ent2_obj).apt_url = apt_url2 + type(m_ent2_obj).apt_suites = suite2 + type(m_ent2_obj).repo_policy_check_tmpl = "{}/ubuntu {}" + m_ent2.return_value = m_ent2_obj + + m_ent_factory.side_effect = [m_ent1, m_ent2] + + if expected is True: + assert entitlements.check_entitlement_apt_directives_are_unique( + mock.MagicMock() + ) + else: + with pytest.raises( + exceptions.EntitlementsAPTDirectivesAreNotUnique + ): + entitlements.check_entitlement_apt_directives_are_unique( + mock.MagicMock(url="test_url") + ) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_esm.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_esm.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_esm.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_esm.py 2024-04-23 13:37:02.000000000 +0000 @@ -22,32 +22,6 @@ return_value=mock.MagicMock(series="xenial"), ) class TestESMEntitlementDisable: - @pytest.mark.parametrize("silent", [False, True]) - @mock.patch(M_PATH + "can_disable", return_value=(False, None)) - def test_disable_returns_false_on_can_disable_false_and_does_nothing( - self, - m_can_disable, - _m_get_release_info, - m_update_apt_and_motd_msgs, - silent, - ): - """When can_disable is false disable returns false and noops.""" - entitlement = ESMInfraEntitlement({}) - - with mock.patch( - "uaclient.apt.remove_auth_apt_repo" - ) as m_remove_apt, mock.patch.object( - entitlement, "setup_local_esm_repo" - ) as m_setup_repo: - ret, fail = entitlement.disable(silent) - assert ret is False - assert fail is None - - assert [mock.call()] == m_can_disable.call_args_list - assert 0 == m_remove_apt.call_count - assert 0 == m_update_apt_and_motd_msgs.call_count - assert 0 == m_setup_repo.call_count - @pytest.mark.parametrize("is_active_esm", (True, False)) @pytest.mark.parametrize("is_lts", (True, False)) @mock.patch("uaclient.entitlements.esm.system.is_current_series_lts") @@ -77,9 +51,9 @@ ) as m_remove_apt_config, mock.patch.object( entitlement, "setup_local_esm_repo" ) as m_setup_repo: - assert entitlement.disable(True) + assert entitlement.disable(mock.MagicMock()) - assert [mock.call(silent=True)] == m_remove_apt_config.call_args_list + assert [mock.call(mock.ANY)] == m_remove_apt_config.call_args_list assert [ mock.call(entitlement.cfg) ] == m_update_apt_and_motd_msgs.call_args_list diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_fips.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_fips.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_fips.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_fips.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,8 +1,6 @@ """Tests related to uaclient.entitlement.base module.""" -import contextlib import copy -import io import logging import os from functools import partial @@ -25,6 +23,7 @@ UBUNTU_FIPS_METAPACKAGE_DEPENDS_FOCAL, UBUNTU_FIPS_METAPACKAGE_DEPENDS_XENIAL, FIPSEntitlement, + FIPSPreviewEntitlement, FIPSUpdatesEntitlement, ) from uaclient.files.notices import Notice, NoticesManager @@ -109,6 +108,54 @@ conditional_packages = entitlement.conditional_packages assert expected == conditional_packages + @pytest.mark.parametrize( + "fips_version, assume_yes, expected_continue", + ( + ( + "0", + True, + True, + ), + ( + "0", + False, + False, + ), + ( + "999", + True, + True, + ), + ( + "999", + False, + True, + ), + ), + ) + @mock.patch(M_PATH + "apt.get_pkg_candidate_version") + @mock.patch(M_PATH + "util.prompt_for_confirmation") + def test_kernel_downgrade( + self, + m_prompt_for_confirmation, + m_pkg_candidate_version, + fips_version, + assume_yes, + expected_continue, + entitlement, + ): + """Test kernel downgrades block install if user denies prompt""" + # if user is prompted for confirmation assume they say no + if not assume_yes: + m_prompt_for_confirmation.return_value = False + else: + m_prompt_for_confirmation.return_value = True + m_pkg_candidate_version.return_value = fips_version + install_continues = entitlement.prompt_if_kernel_downgrade( + assume_yes=assume_yes + ) + assert install_continues == expected_continue + def test_default_repo_key_file(self, entitlement): """GPG keyring file is the same for both FIPS and FIPS with Updates""" assert entitlement.repo_key_file == "ubuntu-pro-fips.gpg" @@ -135,6 +182,14 @@ }, ) ], + "pre_install": [ + ( + entitlement.prompt_if_kernel_downgrade, + { + "assume_yes": assume_yes, + }, + ) + ], "post_enable": None, "pre_disable": [ ( @@ -158,6 +213,14 @@ }, ) ], + "pre_install": [ + ( + entitlement.prompt_if_kernel_downgrade, + { + "assume_yes": assume_yes, + }, + ) + ], "post_enable": None, "pre_disable": [ ( @@ -198,6 +261,14 @@ }, ) ], + "pre_install": [ + ( + entitlement.prompt_if_kernel_downgrade, + { + "assume_yes": False, + }, + ) + ], "post_enable": [messages.FIPS_RUN_APT_UPGRADE], "pre_disable": [ ( @@ -223,6 +294,14 @@ }, ) ], + "pre_install": [ + ( + entitlement.prompt_if_kernel_downgrade, + { + "assume_yes": False, + }, + ) + ], "post_enable": [messages.FIPS_RUN_APT_UPGRADE], "pre_disable": [ ( @@ -244,181 +323,7 @@ assert False, "Unknown entitlement {}".format(entitlement.name) -class TestFIPSEntitlementCanEnable: - @mock.patch("uaclient.util.is_config_value_true", return_value=False) - def test_can_enable_true_on_entitlement_inactive( - self, m_is_config_value_true, capsys, entitlement - ): - """When entitlement is disabled, can_enable returns True.""" - with mock.patch.object( - entitlement, - "applicability_status", - return_value=(ApplicabilityStatus.APPLICABLE, ""), - ): - with mock.patch.object( - entitlement, - "application_status", - return_value=(ApplicationStatus.DISABLED, ""), - ): - with mock.patch.object( - entitlement, - "detect_incompatible_services", - return_value=False, - ): - assert (True, None) == entitlement.can_enable() - assert ("", "") == capsys.readouterr() - - class TestFIPSEntitlementEnable: - @mock.patch("uaclient.apt.update_sources_list") - @mock.patch("uaclient.apt.setup_apt_proxy") - @mock.patch(M_PATH + "get_cloud_type", return_value=("", None)) - def test_enable_configures_apt_sources_and_auth_files( - self, - _m_get_cloud_type, - m_setup_apt_proxy, - m_update_sources_list, - entitlement, - ): - """When entitled, configure apt repo auth token, pinning and url.""" - notice_ent_cls = NoticesManager() - patched_packages = ["a", "b"] - expected_conditional_packages = [ - "openssh-server", - "openssh-server-hmac", - "strongswan", - "strongswan-hmac", - ] - - with contextlib.ExitStack() as stack: - m_add_apt = stack.enter_context( - mock.patch("uaclient.apt.add_auth_apt_repo") - ) - m_add_pinning = stack.enter_context( - mock.patch("uaclient.apt.add_ppa_pinning") - ) - m_installed_pkgs = stack.enter_context( - mock.patch( - "uaclient.apt.get_installed_packages_names", - return_value=["openssh-server", "strongswan"], - ) - ) - m_subp = stack.enter_context( - mock.patch("uaclient.system.subp", return_value=("", "")) - ) - m_can_enable = stack.enter_context( - mock.patch.object(entitlement, "can_enable") - ) - stack.enter_context( - mock.patch("uaclient.util.handle_message_operations") - ) - stack.enter_context( - mock.patch( - "uaclient.system.get_release_info", - return_value=mock.MagicMock(series="xenial"), - ) - ) - stack.enter_context( - mock.patch( - "uaclient.entitlements.fips.system.should_reboot", - return_value=True, - ) - ) - stack.enter_context(mock.patch(M_REPOPATH + "exists")) - stack.enter_context( - mock.patch.object(fips, "services_once_enabled_file") - ) - # Note that this patch uses a PropertyMock and happens on the - # entitlement's type because packages is a property - m_packages = mock.PropertyMock(return_value=patched_packages) - stack.enter_context( - mock.patch.object(type(entitlement), "packages", m_packages) - ) - stack.enter_context( - mock.patch("uaclient.system.is_container", return_value=False) - ) - - m_can_enable.return_value = (True, None) - assert (True, None) == entitlement.enable() - - repo_url = "http://{}".format(entitlement.name.upper()) - add_apt_calls = [ - mock.call( - "/etc/apt/sources.list.d/ubuntu-{}.list".format( - entitlement.name - ), - "{}/ubuntu".format(repo_url), - "{}-token".format(entitlement.name), - ["xenial"], - entitlement.repo_key_file, - ) - ] - apt_pinning_calls = [ - mock.call( - "/etc/apt/preferences.d/ubuntu-{}".format(entitlement.name), - repo_url, - entitlement.origin, - 1001, - ) - ] - - install_cmd = [] - install_cmd.append( - mock.call( - [ - "apt-get", - "install", - "--assume-yes", - "--allow-downgrades", - '-o Dpkg::Options::="--force-confdef"', - '-o Dpkg::Options::="--force-confold"', - ] - + patched_packages, - capture=True, - retry_sleeps=apt.APT_RETRIES, - override_env_vars={"DEBIAN_FRONTEND": "noninteractive"}, - ) - ) - - for pkg in expected_conditional_packages: - install_cmd.append( - mock.call( - [ - "apt-get", - "install", - "--assume-yes", - "--allow-downgrades", - '-o Dpkg::Options::="--force-confdef"', - '-o Dpkg::Options::="--force-confold"', - pkg, - ], - capture=True, - retry_sleeps=apt.APT_RETRIES, - override_env_vars={"DEBIAN_FRONTEND": "noninteractive"}, - ) - ) - - subp_calls = [ - mock.call( - ["apt-mark", "showholds"], - capture=True, - retry_sleeps=apt.APT_RETRIES, - override_env_vars=None, - ), - ] - subp_calls += install_cmd - - assert [mock.call()] == m_can_enable.call_args_list - assert 1 == m_setup_apt_proxy.call_count - assert 1 == m_installed_pkgs.call_count - assert add_apt_calls == m_add_apt.call_args_list - assert apt_pinning_calls == m_add_pinning.call_args_list - assert subp_calls == m_subp.call_args_list - assert 2 == m_update_sources_list.call_count - assert [ - messages.FIPS_SYSTEM_REBOOT_REQUIRED, - ] == notice_ent_cls.list() - @pytest.mark.parametrize( "fips_common_enable_return_value, expected_remove_notice_calls", [ @@ -442,7 +347,7 @@ fips_entitlement = entitlement_factory(FIPSEntitlement) assert ( fips_common_enable_return_value - is fips_entitlement._perform_enable() + is fips_entitlement._perform_enable(mock.MagicMock()) ) assert expected_remove_notice_calls == m_remove_notice.call_args_list @@ -475,130 +380,13 @@ ): m_repo_enable.return_value = repo_enable_return_value with mock.patch.object(fips, "services_once_enabled_file"): - assert repo_enable_return_value is entitlement._perform_enable() + assert repo_enable_return_value is entitlement._perform_enable( + mock.MagicMock() + ) assert ( expected_remove_notice_calls == m_remove_notice.call_args_list[:2] ) - @mock.patch("uaclient.apt.setup_apt_proxy") - @mock.patch("uaclient.apt.add_auth_apt_repo") - @mock.patch( - "uaclient.system.get_release_info", - return_value=mock.MagicMock(series="xenial"), - ) - def test_enable_returns_false_on_missing_suites_directive( - self, - m_get_release_info, - m_add_apt, - _m_setup_apt_proxy, - fips_entitlement_factory, - ): - """When directives do not contain suites returns false.""" - entitlement = fips_entitlement_factory(suites=[]) - - with mock.patch.object( - entitlement, "can_enable", return_value=(True, None) - ): - with mock.patch("uaclient.util.handle_message_operations"): - with pytest.raises(exceptions.UbuntuProError) as excinfo: - entitlement.enable() - error_msg = ( - "Ubuntu Pro server provided no suites directive for {}".format( - entitlement.name - ) - ) - assert error_msg == excinfo.value.msg - assert 0 == m_add_apt.call_count - - @mock.patch("uaclient.apt.setup_apt_proxy") - def test_enable_errors_on_repo_pin_but_invalid_origin( - self, _m_setup_apt_proxy, entitlement - ): - """Error when no valid origin is provided on a pinned entitlemnt.""" - entitlement.origin = None # invalid value - - with contextlib.ExitStack() as stack: - m_add_apt = stack.enter_context( - mock.patch("uaclient.apt.add_auth_apt_repo") - ) - m_add_pinning = stack.enter_context( - mock.patch("uaclient.apt.add_ppa_pinning") - ) - stack.enter_context( - mock.patch.object( - entitlement, "can_enable", return_value=(True, None) - ) - ) - stack.enter_context( - mock.patch("uaclient.util.handle_message_operations") - ) - m_remove_apt_config = stack.enter_context( - mock.patch.object(entitlement, "remove_apt_config") - ) - stack.enter_context( - mock.patch( - "uaclient.system.get_release_info", - return_value=mock.MagicMock(series="xenial"), - ) - ) - stack.enter_context(mock.patch(M_REPOPATH + "exists")) - - with pytest.raises(exceptions.UbuntuProError) as excinfo: - entitlement.enable() - - error_msg = ( - "Cannot setup apt pin. Empty apt repo origin value for {}\n" - "Could not enable {}." - ).format( - entitlement.name, - entitlement.title, - ) - assert error_msg == excinfo.value.msg - assert 0 == m_add_apt.call_count - assert 0 == m_add_pinning.call_count - assert 0 == m_remove_apt_config.call_count - - @mock.patch( - "uaclient.entitlements.fips.get_cloud_type", return_value=("", None) - ) - @mock.patch("uaclient.system.get_release_info") - @mock.patch("uaclient.util.is_config_value_true", return_value=False) - @mock.patch("uaclient.util.prompt_for_confirmation", return_value=False) - @mock.patch("uaclient.util.handle_message_operations") - @mock.patch("uaclient.system.is_container", return_value=False) - def test_fips_enable_fails_when_livepatch_service_is_enabled( - self, - m_is_container, - m_handle_message_op, - m_prompt, - m_is_config_value_true, - m_get_release_info, - m_get_cloud_type, - entitlement_factory, - ): - fips_ent = entitlement_factory(FIPSEntitlement) - m_handle_message_op.return_value = True - base_path = "uaclient.entitlements.livepatch.LivepatchEntitlement" - m_get_release_info.return_value = mock.MagicMock(series="test") - - with mock.patch( - "{}.application_status".format(base_path) - ) as m_livepatch: - with mock.patch.object( - fips_ent, - "applicability_status", - return_value=(ApplicabilityStatus.APPLICABLE, ""), - ): - m_livepatch.return_value = ( - ApplicationStatus.ENABLED, - "", - ) - ret, fail = fips_ent.enable() - - assert not ret - expected_msg = "Cannot enable FIPS when Livepatch is enabled." - assert expected_msg == fail.message.msg - @mock.patch("uaclient.util.handle_message_operations") @mock.patch( M_LIVEPATCH_PATH + "application_status", @@ -627,7 +415,7 @@ ApplicationStatus.ENABLED, "", ) - result, reason = fips_entitlement.enable() + result, reason = fips_entitlement.enable(mock.MagicMock()) assert not result expected_msg = ( "Cannot enable FIPS when FIPS Updates is enabled." @@ -661,7 +449,7 @@ with mock.patch.object( fips, "services_once_enabled_file", fake_dof ): - result, reason = fips_entitlement.enable() + result, reason = fips_entitlement.enable(mock.MagicMock()) assert not result expected_msg = ( "Cannot enable FIPS because FIPS Updates was once enabled." @@ -689,7 +477,7 @@ "{}.application_status".format(base_path) ) as m_livepatch: m_livepatch.return_value = (ApplicationStatus.DISABLED, "") - result, reason = entitlement.enable() + result, reason = entitlement.enable(mock.MagicMock()) assert not result expected_msg = """\ Ubuntu Xenial does not provide a GCP optimized FIPS kernel""" @@ -726,7 +514,7 @@ ApplicationStatus.DISABLED, "", ) - result, reason = entitlement.enable() + result, reason = entitlement.enable(mock.MagicMock()) assert not result expected_msg = """\ Ubuntu Test does not provide a GCP optimized FIPS kernel""" @@ -824,7 +612,7 @@ NoCloudTypeReason.CLOUD_ID_ERROR, ) fips_entitlement = entitlement_factory(FIPSEntitlement) - fips_entitlement._perform_enable() + fips_entitlement._perform_enable(mock.MagicMock()) logs = caplog_text() assert ( "Could not determine cloud, defaulting to generic FIPS package." @@ -895,38 +683,28 @@ assert exc_info.value.msg.strip() == expected_msg -@mock.patch("uaclient.util.handle_message_operations", return_value=True) -@mock.patch("uaclient.system.should_reboot", return_value=True) -@mock.patch( - "uaclient.system.get_release_info", - return_value=mock.MagicMock(series="xenial"), -) -class TestFIPSEntitlementDisable: - def test_disable_on_can_disable_true_removes_apt_config_and_packages( +class TestFIPSCheckForRebootMsg: + @pytest.mark.parametrize( + "operation,expected", + ( + ( + "disable operation", + messages.FIPS_DISABLE_REBOOT_REQUIRED, + ), + ), + ) + @mock.patch("uaclient.system.should_reboot", return_value=True) + def test_check_for_reboot_message( self, - _m_get_release_info, _m_should_reboot, - m_handle_message_operations, + operation, + expected, entitlement, ): """When can_disable, disable removes apt config and packages.""" notice_ent_cls = NoticesManager() - - with mock.patch.object( - entitlement, "can_disable", return_value=(True, None) - ): - with mock.patch.object( - entitlement, "remove_apt_config" - ) as m_remove_apt_config: - with mock.patch.object( - entitlement, "remove_packages" - ) as m_remove_packages: - assert entitlement.disable(True) - assert [mock.call(silent=True)] == m_remove_apt_config.call_args_list - assert [mock.call()] == m_remove_packages.call_args_list - assert [ - messages.FIPS_DISABLE_REBOOT_REQUIRED, - ] == notice_ent_cls.list() + entitlement._check_for_reboot_msg(operation=operation) + assert [expected] == notice_ent_cls.list() @mock.patch("uaclient.system.should_reboot") @@ -935,8 +713,15 @@ "super_application_status", [s for s in ApplicationStatus if s is not ApplicationStatus.ENABLED], ) + @mock.patch("uaclient.system.is_container", return_value=False) + @mock.patch("os.path.exists", return_value=False) def test_non_enabled_passed_through( - self, _m_should_reboot, entitlement, super_application_status + self, + _m_path_exists, + _m_is_container, + _m_should_reboot, + entitlement, + super_application_status, ): msg = "sure is some status here" with mock.patch( @@ -951,8 +736,15 @@ "super_application_status", [s for s in ApplicationStatus if s is not ApplicationStatus.ENABLED], ) + @mock.patch("uaclient.system.is_container", return_value=False) + @mock.patch("os.path.exists", return_value=False) def test_non_root_does_not_fail( - self, _m_should_reboot, super_application_status, FakeConfig + self, + _m_path_exists, + _m_is_container, + _m_should_reboot, + super_application_status, + FakeConfig, ): cfg = FakeConfig() entitlement = FIPSUpdatesEntitlement(cfg) @@ -968,8 +760,10 @@ @pytest.mark.parametrize("should_reboot", ((True), (False))) @pytest.mark.parametrize("path_exists", ((True), (False))) @pytest.mark.parametrize("proc_content", (("0"), ("1"))) + @mock.patch("uaclient.system.is_container", return_value=False) def test_proc_file_is_used_to_determine_application_status_message( self, + _m_is_container, m_should_reboot, proc_content, path_exists, @@ -1066,6 +860,35 @@ assert expected_msg.msg == actual_msg.msg assert expected_msg.name == actual_msg.name + @mock.patch("uaclient.system.is_container", return_value=True) + def test_application_status_inside_container( + self, + _m_is_container, + m_should_reboot, + entitlement, + ): + m_should_reboot.return_value = False + notice_ent_cls = NoticesManager() + notice_ent_cls.add( + Notice.FIPS_SYSTEM_REBOOT_REQUIRED, + messages.FIPS_SYSTEM_REBOOT_REQUIRED, + ) + expected_status = ApplicationStatus.ENABLED + expected_msg = "test" + + with mock.patch( + M_PATH + "repo.RepoEntitlement.application_status", + return_value=( + expected_status, + messages.NamedMessage("test", expected_msg), + ), + ): + actual_status, actual_message = entitlement.application_status() + + assert expected_status == actual_status + assert expected_msg == actual_message.msg + assert [] == notice_ent_cls.list() + def test_fips_does_not_show_enabled_when_fips_updates_is( self, _m_should_reboot, entitlement ): @@ -1093,7 +916,7 @@ m_run_apt.side_effect = fakes.FakeUbuntuProError() with mock.patch.object(entitlement, "remove_apt_config"): with pytest.raises(exceptions.UbuntuProError): - entitlement.install_packages() + entitlement.install_packages(mock.MagicMock()) @mock.patch(M_PATH + "apt.get_installed_packages_names") @mock.patch(M_PATH + "apt.run_apt_install_command") @@ -1104,7 +927,6 @@ fips_entitlement_factory, event, ): - conditional_pkgs = ["b", "c"] m_installed_pkgs.return_value = conditional_pkgs packages = ["a"] @@ -1116,12 +938,11 @@ fakes.FakeUbuntuProError(), ] - fake_stdout = io.StringIO() - with contextlib.redirect_stdout(fake_stdout): - with mock.patch.object( - type(entitlement), "conditional_packages", conditional_pkgs - ): - entitlement.install_packages() + progress_mock = mock.MagicMock() + with mock.patch.object( + type(entitlement), "conditional_packages", conditional_pkgs + ): + entitlement.install_packages(progress_mock) install_cmds = [] all_pkgs = packages + conditional_pkgs @@ -1138,23 +959,27 @@ ) ) - expected_msg = "\n".join( - [ - "Installing {} packages".format(entitlement.title), - "Updating standard Ubuntu package lists", - "Could not enable {}.".format(entitlement.title), + assert [ + mock.call("message_operation", mock.ANY), + mock.call("info", "Updating standard Ubuntu package lists"), + mock.call( + "info", messages.FIPS_PACKAGE_NOT_AVAILABLE.format( service=entitlement.title, pkg="b" ), - "Could not enable {}.".format(entitlement.title), + ), + mock.call( + "info", messages.FIPS_PACKAGE_NOT_AVAILABLE.format( service=entitlement.title, pkg="c" ), - ] - ) + ), + ] == progress_mock.emit.call_args_list + assert [ + mock.call("Installing {} packages".format(entitlement.title)), + ] == progress_mock.progress.call_args_list assert install_cmds == m_run_apt_install.call_args_list - assert expected_msg.strip() in fake_stdout.getvalue().strip() class TestFipsSetupAPTConfig: @@ -1185,7 +1010,7 @@ ): """Unmark only fips-specific package holds if present.""" run_apt_command.return_value = held_packages - entitlement.setup_apt_config(silent=False) + entitlement.setup_apt_config(mock.MagicMock()) expected_calls = [ mock.call( ["apt-mark", "showholds"], @@ -1205,7 +1030,7 @@ ) ) assert expected_calls == run_apt_command.call_args_list - assert [mock.call(silent=False)] == setup_apt_config.call_args_list + assert [mock.call(mock.ANY)] == setup_apt_config.call_args_list class TestFipsEntitlementPackages: @@ -1220,6 +1045,12 @@ assert isinstance(entitlement.packages, list) + @mock.patch("uaclient.system.is_container", return_value=True) + def test_packages_is_empty_if_running_on_container( + self, _m_is_container, entitlement + ): + assert [] == entitlement.packages + @mock.patch(M_PATH + "apt.get_installed_packages_names", return_value=[]) @mock.patch("uaclient.system.get_release_info") def test_fips_required_packages_included( @@ -1291,7 +1122,10 @@ fips_updates_ent = entitlement_factory( FIPSUpdatesEntitlement, cfg=cfg ) - assert fips_updates_ent._perform_enable() == enable_ret + assert ( + fips_updates_ent._perform_enable(mock.MagicMock()) + == enable_ret + ) if enable_ret: assert 1 == m_services_once_enabled.write.call_count @@ -1299,13 +1133,20 @@ assert not m_services_once_enabled.write.call_count -class TestFIPSUpdatesEntitlementCanEnable: +class TestFIPSEntitlementCanEnable: + @pytest.mark.parametrize( + "fips_cls", + ( + (FIPSUpdatesEntitlement), + (FIPSPreviewEntitlement), + ), + ) @mock.patch("uaclient.util.is_config_value_true", return_value=False) def test_can_enable_false_if_fips_enabled( - self, m_is_config_value_true, capsys, entitlement_factory + self, m_is_config_value_true, fips_cls, capsys, entitlement_factory ): """When entitlement is disabled, can_enable returns True.""" - entitlement = entitlement_factory(FIPSUpdatesEntitlement) + entitlement = entitlement_factory(fips_cls) with mock.patch.object( entitlement, "applicability_status", @@ -1329,3 +1170,11 @@ reason.reason == CanEnableFailureReason.INCOMPATIBLE_SERVICE ) + + +class TestFipsPreview: + def test_allow_fips_on_cloud_is_always_true(self, entitlement_factory): + entitlement = entitlement_factory(FIPSPreviewEntitlement) + assert entitlement._allow_fips_on_cloud_instance( + series="test", cloud_id="test" + ) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_landscape.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_landscape.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_landscape.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_landscape.py 2024-04-23 13:37:02.000000000 +0000 @@ -93,7 +93,7 @@ landscape = LandscapeEntitlement( FakeConfig(), assume_yes=assume_yes, extra_args=extra_args ) - assert expected_result == landscape._perform_enable() + assert expected_result == landscape._perform_enable(mock.MagicMock()) assert expected_subp_calls == m_subp.call_args_list @pytest.mark.parametrize( @@ -126,7 +126,7 @@ ): m_subp.side_effect = subp_sideeffect landscape = LandscapeEntitlement(FakeConfig()) - assert expected_result == landscape._perform_disable() + assert expected_result == landscape._perform_disable(mock.MagicMock()) assert expected_subp_calls == m_subp.call_args_list @pytest.mark.parametrize( diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_livepatch.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_livepatch.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_livepatch.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_livepatch.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,15 +1,13 @@ """Tests related to uaclient.entitlement.livepatch module.""" -import contextlib import copy -import io import logging from functools import partial import mock import pytest -from uaclient import apt, exceptions, livepatch, messages, system +from uaclient import apt, exceptions, livepatch, messages from uaclient.entitlements.entitlement_status import ( ApplicabilityStatus, ApplicationStatus, @@ -21,7 +19,6 @@ LivepatchEntitlement, process_config_directives, ) -from uaclient.entitlements.tests.conftest import machine_token from uaclient.snap import SNAP_CMD from uaclient.testing import fakes @@ -96,23 +93,6 @@ expected_details = "Cannot install Livepatch on a container." assert expected_details == details.msg - def test_user_facing_status_unavailable_on_unentitled(self, entitlement): - """Status UNAVAILABLE on absent entitlement contract status.""" - no_entitlements = machine_token(LivepatchEntitlement.name) - # Delete livepatch entitlement info - no_entitlements["machineTokenInfo"]["contractInfo"][ - "resourceEntitlements" - ].pop() - entitlement.cfg.machine_token_file.write(no_entitlements) - - with mock.patch( - "uaclient.system.get_release_info" - ) as m_get_release_info: - m_get_release_info.return_value = mock.MagicMock(series="xenial") - uf_status, details = entitlement.user_facing_status() - assert uf_status == UserFacingStatus.UNAVAILABLE - assert "Livepatch is not entitled" == details.msg - class TestLivepatchProcessConfigDirectives: @pytest.mark.parametrize( @@ -180,70 +160,6 @@ "uaclient.entitlements.livepatch.system.is_container", return_value=False ) class TestLivepatchEntitlementCanEnable: - @pytest.mark.parametrize( - "supported_kernel_ver", - ( - system.KernelInfo( - uname_machine_arch="", - uname_release="4.4.0-00-generic", - proc_version_signature_version="", - build_date=None, - major=4, - minor=4, - patch=0, - abi="00", - flavor="generic", - ), - system.KernelInfo( - uname_machine_arch="", - uname_release="5.0.0-00-generic", - proc_version_signature_version="", - build_date=None, - major=5, - minor=0, - patch=0, - abi="00", - flavor="generic", - ), - system.KernelInfo( - uname_machine_arch="", - uname_release="4.19.0-00-generic", - proc_version_signature_version="", - build_date=None, - major=4, - minor=19, - patch=0, - abi="00", - flavor="generic", - ), - ), - ) - @mock.patch("uaclient.system.get_dpkg_arch", return_value="x86_64") - @mock.patch("uaclient.system.get_kernel_info") - @mock.patch( - "uaclient.system.get_release_info", - return_value=mock.MagicMock(series="xenial"), - ) - def test_can_enable_true_on_entitlement_inactive( - self, - _m_get_release_info, - m_kernel_info, - _m_dpkg_arch, - _m_is_container, - _m_livepatch_status, - _m_fips_status, - supported_kernel_ver, - capsys, - entitlement, - ): - """When entitlement is INACTIVE, can_enable returns True.""" - m_kernel_info.return_value = supported_kernel_ver - with mock.patch("uaclient.system.is_container") as m_container: - m_container.return_value = False - assert (True, None) == entitlement.can_enable() - assert ("", "") == capsys.readouterr() - assert [mock.call()] == m_container.call_args_list - @mock.patch( "uaclient.system.get_release_info", return_value=mock.MagicMock(series="xenial"), @@ -330,6 +246,7 @@ if any([process_directives, process_token]): setup_calls = [ mock.call( + progress=mock.ANY, process_directives=process_directives, process_token=process_token, ) @@ -363,6 +280,7 @@ if any([process_directives, process_token]): setup_calls = [ mock.call( + progress=mock.ANY, process_directives=process_directives, process_token=process_token, ) @@ -371,6 +289,21 @@ setup_calls = [] assert setup_calls == m_setup_livepatch_config.call_args_list + @mock.patch(M_PATH + "LivepatchEntitlement.enable") + @mock.patch(M_PATH + "LivepatchEntitlement.application_status") + def test_livepatch_disable_and_delta_with_enable_by_default( + self, + m_application_status, + m_enable, + entitlement, + ): + deltas = {"entitlement": {"obligations": {"enableByDefault": True}}} + m_enable.return_value = (True, None) + application_status = ApplicationStatus.DISABLED + m_application_status.return_value = (application_status, "") + entitlement.process_contract_deltas({}, deltas, False) + assert [mock.call(mock.ANY)] == m_enable.call_args_list + @mock.patch(M_PATH + "snap.is_snapd_installed_as_a_snap") @mock.patch(M_PATH + "snap.is_snapd_installed") @@ -482,19 +415,26 @@ m_update_sources_list.side_effect = fake_update_sources_list - assert entitlement.enable() + progress_mock = mock.MagicMock() + assert entitlement.enable(progress_mock) assert self.mocks_install + self.mocks_config in m_subp.call_args_list assert self.mocks_apt_update == m_run_apt_update.call_args_list assert 1 == m_update_sources_list.call_count - msg = ( - "Installing snapd\n" - "Updating standard Ubuntu package lists\n" - "Installing snapd snap\n" - "Installing canonical-livepatch snap\n" - "Disabling Livepatch prior to re-attach with new token\n" - "Canonical Livepatch enabled\n" - ) - assert (msg, "") == capsys.readouterr() + assert [ + mock.call("Installing Livepatch"), + mock.call("Setting up Livepatch"), + ] == progress_mock.progress.call_args_list + assert [ + mock.call("message_operation", None), + mock.call("message_operation", None), + mock.call("info", "Installing snapd"), + mock.call("info", "Installing snapd snap"), + mock.call("info", "Installing canonical-livepatch snap"), + mock.call( + "info", "Disabling Livepatch prior to re-attach with new token" + ), + ] == progress_mock.emit.call_args_list + assert [mock.call(livepatch.LIVEPATCH_CMD)] == m_which.call_args_list expected_log = ( "DEBUG Trying to install snapd." " Ignoring apt-get update failure: This is a test" @@ -557,20 +497,26 @@ None, ] - assert entitlement.enable() + progress_mock = mock.MagicMock() + assert entitlement.enable(progress_mock) assert self.mocks_install + self.mocks_config in m_subp.call_args_list assert self.mocks_apt_update == m_run_apt_update.call_args_list assert 1 == m_update_sources_list.call_count - msg = ( - "Installing snapd\n" - "Updating standard Ubuntu package lists\n" - "Installing snapd snap\n" - "Executing `snap install snapd` failed.\n" - "Installing canonical-livepatch snap\n" - "Disabling Livepatch prior to re-attach with new token\n" - "Canonical Livepatch enabled\n" - ) - assert (msg, "") == capsys.readouterr() + assert [ + mock.call("Installing Livepatch"), + mock.call("Setting up Livepatch"), + ] == progress_mock.progress.call_args_list + assert [ + mock.call("message_operation", None), + mock.call("message_operation", None), + mock.call("info", "Installing snapd"), + mock.call("info", "Installing snapd snap"), + mock.call("info", "Executing `snap install snapd` failed."), + mock.call("info", "Installing canonical-livepatch snap"), + mock.call( + "info", "Disabling Livepatch prior to re-attach with new token" + ), + ] == progress_mock.emit.call_args_list assert [mock.call(livepatch.LIVEPATCH_CMD)] == m_which.call_args_list assert m_validate_proxy.call_count == 2 assert m_snap_proxy.call_count == 1 @@ -606,7 +552,8 @@ m_app_status.return_value = application_status, "enabled" m_is_snapd_installed.return_value = True - assert entitlement.enable() + progress_mock = mock.MagicMock() + assert entitlement.enable(progress_mock) assert ( self.mocks_snap_wait_seed + self.mocks_snapd_refresh @@ -614,12 +561,18 @@ + self.mocks_config in m_subp.call_args_list ) - msg = ( - "Installing canonical-livepatch snap\n" - "Disabling Livepatch prior to re-attach with new token\n" - "Canonical Livepatch enabled\n" - ) - assert (msg, "") == capsys.readouterr() + assert [ + mock.call("Installing Livepatch"), + mock.call("Setting up Livepatch"), + ] == progress_mock.progress.call_args_list + assert [ + mock.call("message_operation", None), + mock.call("message_operation", None), + mock.call("info", "Installing canonical-livepatch snap"), + mock.call( + "info", "Disabling Livepatch prior to re-attach with new token" + ), + ] == progress_mock.emit.call_args_list assert [mock.call(livepatch.LIVEPATCH_CMD)] == m_which.call_args_list assert m_validate_proxy.call_count == 2 assert m_snap_proxy.call_count == 1 @@ -657,7 +610,8 @@ m_app_status.return_value = application_status, "enabled" m_is_snapd_installed.return_value = True - assert entitlement.enable() + progress_mock = mock.MagicMock() + assert entitlement.enable(progress_mock) subp_calls = [ mock.call( [SNAP_CMD, "wait", "system", "seed.loaded"], capture=True @@ -678,11 +632,13 @@ ), ] assert subp_calls == m_subp.call_args_list - assert ( - "Disabling Livepatch prior to re-attach with new token\n" - "Canonical Livepatch enabled\n", - "", - ) == capsys.readouterr() + assert [ + mock.call("message_operation", None), + mock.call("message_operation", None), + mock.call( + "info", "Disabling Livepatch prior to re-attach with new token" + ), + ] == progress_mock.emit.call_args_list assert m_validate_proxy.call_count == 2 assert m_snap_proxy.call_count == 1 assert m_livepatch_proxy.call_count == 1 @@ -717,7 +673,7 @@ m_app_status.return_value = ApplicationStatus.DISABLED, "nope" m_is_snapd_installed.return_value = True - assert entitlement.enable() + assert entitlement.enable(mock.MagicMock()) subp_no_livepatch_disable = [ mock.call( [SNAP_CMD, "wait", "system", "seed.loaded"], capture=True @@ -737,50 +693,10 @@ ), ] assert subp_no_livepatch_disable == m_subp.call_args_list - assert ("Canonical Livepatch enabled\n", "") == capsys.readouterr() assert m_validate_proxy.call_count == 2 assert m_snap_proxy.call_count == 1 assert m_livepatch_proxy.call_count == 1 - @pytest.mark.parametrize( - "cls_name, cls_title", (("FIPSEntitlement", "FIPS"),) - ) - @mock.patch("uaclient.util.handle_message_operations") - @mock.patch("uaclient.system.is_container", return_value=False) - def test_enable_fails_when_blocking_service_is_enabled( - self, - m_is_container, - m_handle_message_op, - m_livepatch_proxy, - m_snap_proxy, - m_validate_proxy, - _m_is_snapd_installed, - m_is_snapd_installed_as_a_snap, - cls_name, - cls_title, - entitlement, - ): - m_handle_message_op.return_value = True - - with mock.patch(M_LIVEPATCH_STATUS, return_value=DISABLED_APP_STATUS): - with mock.patch( - "uaclient.entitlements.fips.{}.application_status".format( - cls_name - ) - ) as m_fips: - m_fips.return_value = (ApplicationStatus.ENABLED, "") - result, reason = entitlement.enable() - assert not result - - msg = "Cannot enable Livepatch when {} is enabled.".format( - cls_title - ) - assert msg.strip() == reason.message.msg.strip() - - assert m_validate_proxy.call_count == 0 - assert m_snap_proxy.call_count == 0 - assert m_livepatch_proxy.call_count == 0 - @pytest.mark.parametrize("caplog_text", [logging.WARN], indirect=True) @mock.patch("uaclient.system.which", return_value=None) @mock.patch("uaclient.system.subp") @@ -818,22 +734,21 @@ True, ] - fake_stdout = io.StringIO() + progress_mock = mock.MagicMock() with mock.patch.object(entitlement, "can_enable") as m_can_enable: m_can_enable.return_value = (True, None) with mock.patch.object( entitlement, "setup_livepatch_config" ) as m_setup_livepatch: - with contextlib.redirect_stdout(fake_stdout): - entitlement.enable() + entitlement.enable(progress_mock) assert 1 == m_can_enable.call_count assert 1 == m_setup_livepatch.call_count assert ( - "Installing canonical-livepatch snap" - in fake_stdout.getvalue().strip() + mock.call("info", "Installing canonical-livepatch snap") + in progress_mock.emit.call_args_list ) assert ( @@ -872,7 +787,7 @@ entitlement, "setup_livepatch_config" ) as m_setup_livepatch: with pytest.raises(exceptions.CannotInstallSnapdError): - entitlement.enable() + entitlement.enable(mock.MagicMock()) assert 1 == m_can_enable.call_count assert 0 == m_setup_livepatch.call_count @@ -914,7 +829,7 @@ with pytest.raises( exceptions.ProcessExecutionError ) as excinfo: - entitlement.enable() + entitlement.enable(mock.MagicMock()) assert 1 == m_can_enable.call_count assert 0 == m_setup_livepatch.call_count diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_repo.py ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_repo.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/entitlements/tests/test_repo.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/entitlements/tests/test_repo.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,5 +1,3 @@ -import copy - import mock import pytest @@ -7,10 +5,9 @@ from uaclient.entitlements.entitlement_status import ( ApplicabilityStatus, ApplicationStatus, - UserFacingStatus, ) from uaclient.entitlements.repo import RepoEntitlement -from uaclient.entitlements.tests.conftest import machine_token +from uaclient.testing.helpers import does_not_raise M_PATH = "uaclient.entitlements.repo." M_CONTRACT_PATH = "uaclient.entitlements.repo.contract.UAContractClient." @@ -29,6 +26,11 @@ repo_pin_priority = 1000 +class RepoTestEntitlementRepoWithRemovePackages(RepoTestEntitlement): + def remove_packages(self): + pass + + @pytest.fixture def entitlement(entitlement_factory): return entitlement_factory( @@ -36,41 +38,6 @@ ) -class TestUserFacingStatus: - @mock.patch(M_PATH + "system.get_release_info") - def test_inapplicable_on_inapplicable_applicability_status( - self, m_release_info, entitlement - ): - """When applicability_status is INAPPLICABLE, return INAPPLICABLE.""" - m_release_info.return_value = mock.MagicMock( - series="example", pretty_version="version" - ) - applicability, details = entitlement.applicability_status() - assert ApplicabilityStatus.INAPPLICABLE == applicability - expected_details = ( - "Repo Test Class is not available for Ubuntu version." - ) - assert expected_details == details.msg - uf_status, _ = entitlement.user_facing_status() - assert UserFacingStatus.INAPPLICABLE == uf_status - - @mock.patch(M_PATH + "system.get_release_info") - def test_unavailable_on_unentitled(self, m_release_info, entitlement): - """When unentitled, return UNAVAILABLE.""" - no_entitlements = copy.deepcopy(machine_token("blah")) - # delete all enttlements - no_entitlements["machineTokenInfo"]["contractInfo"][ - "resourceEntitlements" - ].pop() - entitlement.cfg.machine_token_file.write(no_entitlements) - m_release_info.return_value = mock.MagicMock(series="xenial") - applicability, _details = entitlement.applicability_status() - assert ApplicabilityStatus.APPLICABLE == applicability - uf_status, uf_details = entitlement.user_facing_status() - assert UserFacingStatus.UNAVAILABLE == uf_status - assert "Repo Test Class is not entitled" == uf_details.msg - - class TestProcessContractDeltas: @pytest.mark.parametrize("orig_access", ({}, {"entitlement": {}})) def test_on_no_deltas(self, orig_access): @@ -88,7 +55,9 @@ @pytest.mark.parametrize("entitled", (False, util.DROPPED_KEY)) @mock.patch.object(RepoTestEntitlement, "disable") - @mock.patch.object(RepoTestEntitlement, "can_disable", return_value=True) + @mock.patch.object( + RepoTestEntitlement, "can_disable", return_value=(True, None) + ) @mock.patch.object(RepoTestEntitlement, "application_status") def test_disable_when_delta_to_unentitled( self, @@ -105,7 +74,7 @@ {"entitlement": {"entitled": True}}, {"entitlement": {"entitled": entitled}}, ) - assert [mock.call()] == m_disable.call_args_list + assert [mock.call(mock.ANY)] == m_disable.call_args_list @mock.patch.object(RepoTestEntitlement, "remove_apt_config") @mock.patch.object(RepoTestEntitlement, "application_status") @@ -162,7 +131,7 @@ }, allow_enable=True, ) - assert [mock.call()] == m_enable.call_args_list + assert [mock.call(mock.ANY)] == m_enable.call_args_list @mock.patch.object(RepoTestEntitlement, "enable") @mock.patch.object(RepoTestEntitlement, "application_status") @@ -200,6 +169,9 @@ assert expected_msg in capsys.readouterr()[1] @pytest.mark.parametrize("packages", ([], ["extremetuxracer"])) + @mock.patch( + "uaclient.files.state_files.status_cache_file.read", return_value=None + ) @mock.patch.object(RepoTestEntitlement, "install_packages") @mock.patch(M_PATH + "apt.remove_auth_apt_repo") @mock.patch.object(RepoTestEntitlement, "setup_apt_config") @@ -214,6 +186,7 @@ m_setup_apt_config, m_remove_auth_apt_repo, m_install_packages, + _m_status_cache, packages, entitlement, ): @@ -233,11 +206,11 @@ assert entitlement.process_contract_deltas( {"entitlement": {"entitled": True}}, deltas ) - assert [mock.call()] == m_remove_apt_config.call_args_list - assert [mock.call()] == m_setup_apt_config.call_args_list + assert [mock.call(mock.ANY)] == m_remove_apt_config.call_args_list + assert [mock.call(mock.ANY)] == m_setup_apt_config.call_args_list if packages: assert [ - mock.call(package_list=packages) + mock.call(mock.ANY, package_list=packages) ] == m_install_packages.call_args_list else: assert 0 == m_install_packages.call_count @@ -247,10 +220,10 @@ @pytest.mark.parametrize( "series,file_extension", (("jammy", "list"), ("noble", "sources")) ) + @mock.patch("uaclient.files.state_files.status_cache_file.read") @mock.patch( "uaclient.entitlements.base.UAEntitlement.process_contract_deltas" ) - @mock.patch("uaclient.config.UAConfig.read_cache") @mock.patch(M_PATH + "system.get_release_info") @mock.patch(M_PATH + "apt.remove_auth_apt_repo") @mock.patch.object(RepoTestEntitlement, "setup_apt_config") @@ -263,8 +236,8 @@ m_setup_apt_config, m_remove_auth_apt_repo, m_release_info, - m_read_cache, m_process_contract_deltas, + m_status_cache_read, series, file_extension, entitlement, @@ -272,7 +245,7 @@ """Remove old apt url when aptURL delta occurs on active service.""" m_check_apt_url_applied.return_value = False m_process_contract_deltas.return_value = False - m_read_cache.return_value = { + m_status_cache_read.return_value = { "services": [{"name": "repotest", "status": "enabled"}] } m_release_info.return_value.series = series @@ -291,8 +264,8 @@ "resourceToken": "repotest-token", }, ) - assert [mock.call()] == m_remove_apt_config.call_args_list - assert [mock.call()] == m_setup_apt_config.call_args_list + assert [mock.call(mock.ANY)] == m_remove_apt_config.call_args_list + assert [mock.call(mock.ANY)] == m_setup_apt_config.call_args_list apt_auth_remove_calls = [ mock.call( "/etc/apt/sources.list.d/ubuntu-repotest.{}".format( @@ -303,9 +276,9 @@ ] assert apt_auth_remove_calls == m_remove_auth_apt_repo.call_args_list assert [ - mock.call("status-cache"), - mock.call("status-cache"), - ] == m_read_cache.call_args_list + mock.call(), + mock.call(), + ] == m_status_cache_read.call_args_list assert 1 == m_process_contract_deltas.call_count assert [ @@ -316,7 +289,7 @@ @mock.patch( "uaclient.entitlements.base.UAEntitlement.process_contract_deltas" ) - @mock.patch("uaclient.config.UAConfig.read_cache") + @mock.patch("uaclient.files.state_files.status_cache_file.read") @mock.patch(M_PATH + "system.get_release_info") @mock.patch(M_PATH + "apt.remove_auth_apt_repo") @mock.patch.object(RepoTestEntitlement, "setup_apt_config") @@ -329,14 +302,14 @@ m_setup_apt_config, m_remove_auth_apt_repo, m_release_info, - m_read_cache, + m_status_cache_read, m_process_contract_deltas, entitlement, ): """Do not change system if apt url delta is already applied.""" m_check_apt_url_applied.return_value = True m_process_contract_deltas.return_value = False - m_read_cache.return_value = { + m_status_cache_read.return_value = { "services": [{"name": "repotest", "status": "enabled"}] } assert entitlement.process_contract_deltas( @@ -359,9 +332,9 @@ assert m_remove_auth_apt_repo.call_count == 0 assert [ - mock.call("status-cache"), - mock.call("status-cache"), - ] == m_read_cache.call_args_list + mock.call(), + mock.call(), + ] == m_status_cache_read.call_args_list assert 1 == m_process_contract_deltas.call_count assert [ @@ -371,63 +344,6 @@ class TestRepoEnable: - @pytest.mark.parametrize( - "pre_disable_msg,post_disable_msg,output,retval", - ( - ( - ["pre1", (lambda: False, {}), "pre2"], - ["post1"], - "pre1\n", - False, - ), - (["pre1", (lambda: True, {}), "pre2"], [], "pre1\npre2\n", True), - ( - ["pre1", (lambda: True, {}), "pre2"], - ["post1", (lambda: False, {}), "post2"], - "pre1\npre2\npost1\n", - False, - ), - ( - ["pre1", (lambda: True, {}), "pre2"], - ["post1", (lambda: True, {}), "post2"], - "pre1\npre2\npost1\npost2\n", - True, - ), - ), - ) - @mock.patch(M_PATH + "system.subp", return_value=("", "")) - @mock.patch(M_PATH + "system.should_reboot") - @mock.patch.object(RepoTestEntitlement, "remove_apt_config") - @mock.patch.object( - RepoTestEntitlement, "can_disable", return_value=(True, None) - ) - def test_enable_can_exit_on_pre_or_post_disable_messaging_hooks( - self, - _can_disable, - remove_apt_config, - m_should_reboot, - m_subp, - pre_disable_msg, - post_disable_msg, - output, - retval, - entitlement, - capsys, - event, - ): - messaging = { - "pre_disable": pre_disable_msg, - "post_disable": post_disable_msg, - } - m_should_reboot.return_value = False - with mock.patch.object(type(entitlement), "messaging", messaging): - with mock.patch.object(type(entitlement), "packages", []): - ret, fail = entitlement.disable() - assert retval == ret - stdout, _ = capsys.readouterr() - assert output == stdout - - @pytest.mark.parametrize("should_reboot", (False, True)) @pytest.mark.parametrize("with_pre_install_msg", (False, True)) @pytest.mark.parametrize("packages", (["a"], [], None)) @pytest.mark.parametrize( @@ -435,7 +351,6 @@ ) @mock.patch("uaclient.apt.update_sources_list") @mock.patch("uaclient.apt.setup_apt_proxy") - @mock.patch(M_PATH + "system.should_reboot") @mock.patch(M_PATH + "system.subp", return_value=("", "")) @mock.patch(M_PATH + "apt.add_auth_apt_repo") @mock.patch(M_PATH + "exists", return_value=True) @@ -450,7 +365,6 @@ m_exists, m_apt_add, m_subp, - m_should_reboot, m_setup_apt_proxy, m_update_sources_list, entitlement, @@ -461,11 +375,9 @@ series, file_extension, with_pre_install_msg, - should_reboot, ): """On enable add authenticated apt repo and refresh package lists.""" m_release_info.return_value = mock.MagicMock(series=series) - m_should_reboot.return_value = should_reboot pre_install_msgs = ["Some pre-install information", "Some more info"] if with_pre_install_msg: @@ -478,18 +390,14 @@ messaging_patch = mock.MagicMock() expected_apt_calls = [] - - reboot_msg = "A reboot is required to complete install." - expected_output = ( - "\n".join( - [ - "Updating Repo Test Class package lists", - "Repo Test Class enabled", - ] - + ([reboot_msg] if should_reboot else []) - ) - + "\n" - ) + expected_emit_calls = [ + mock.call("message_operation", None), + mock.call("message_operation", None), + ] + expected_progress_calls = [ + mock.call("Configuring APT access to Repo Test Class"), + mock.call("Updating Repo Test Class package lists"), + ] update_sources_list_call_count = 1 if packages is not None: @@ -508,26 +416,30 @@ override_env_vars=None, ) ) - expected_output = ( - "\n".join( - ["Updating Repo Test Class package lists"] - + (pre_install_msgs if with_pre_install_msg else []) - + ["Updating standard Ubuntu package lists"] - + [ - "Installing Repo Test Class packages", - "Repo Test Class enabled", - ] - + ([reboot_msg] if should_reboot else []) - ) - + "\n" - ) + expected_emit_calls = [ + mock.call("message_operation", None), + mock.call("message_operation", None), + mock.call( + "message_operation", + pre_install_msgs if with_pre_install_msg else None, + ), + mock.call( + "info", "Updating standard Ubuntu package lists" + ), + ] + expected_progress_calls = [ + mock.call("Configuring APT access to Repo Test Class"), + mock.call("Updating Repo Test Class package lists"), + mock.call("Installing Repo Test Class packages"), + ] else: packages = entitlement.packages + progress_mock = mock.MagicMock() # We patch the type of entitlement because packages is a property with mock.patch.object(type(entitlement), "packages", packages): with messaging_patch: - entitlement.enable() + entitlement.enable(progress_mock) expected_calls = [ mock.call(apt.APT_METHOD_HTTPS_FILE), @@ -550,10 +462,9 @@ ) ] assert add_apt_calls == m_apt_add.call_args_list - assert 1 == m_should_reboot.call_count assert 1 == m_setup_apt_proxy.call_count - stdout, _ = capsys.readouterr() - assert expected_output == stdout + assert expected_emit_calls == progress_mock.emit.call_args_list + assert expected_progress_calls == progress_mock.progress.call_args_list @mock.patch("uaclient.apt.setup_apt_proxy") @mock.patch(M_PATH + "system.subp") @@ -578,7 +489,7 @@ with mock.patch.object( entitlement, "remove_apt_config" ) as m_rac: - entitlement.enable() + entitlement.enable(mock.MagicMock()) assert ( "Unexpected APT error.\n" @@ -596,35 +507,30 @@ "access_only", "expected_setup_apt_calls", "expected_install_calls", - "expected_check_for_reboot_calls", ], [ ( False, False, - [mock.call(silent=mock.ANY)], - [mock.call()], - [mock.call(operation="install")], + [mock.call(mock.ANY)], + [mock.call(mock.ANY)], ), ( False, True, - [mock.call(silent=mock.ANY)], - [mock.call()], - [mock.call(operation="install")], + [mock.call(mock.ANY)], + [mock.call(mock.ANY)], ), ( True, False, - [mock.call(silent=mock.ANY)], - [mock.call()], - [mock.call(operation="install")], + [mock.call(mock.ANY)], + [mock.call(mock.ANY)], ), ( True, True, - [mock.call(silent=mock.ANY)], - [], + [mock.call(mock.ANY)], [], ), ], @@ -641,7 +547,6 @@ access_only, expected_setup_apt_calls, expected_install_calls, - expected_check_for_reboot_calls, entitlement_factory, ): with mock.patch.object( @@ -652,18 +557,14 @@ affordances={"series": ["xenial"]}, access_only=access_only, ) - assert entitlement._perform_enable(silent=True) is True + assert entitlement._perform_enable(mock.MagicMock()) is True assert ( m_setup_apt_config.call_args_list == expected_setup_apt_calls ) assert m_install_packages.call_args_list == expected_install_calls - assert ( - m_check_for_reboot_msg.call_args_list - == expected_check_for_reboot_calls - ) -class TestPerformDisable: +class TestRepoPerformDisable: @pytest.mark.parametrize("purge_value", (True, False)) @mock.patch(M_PATH + "apt.get_installed_packages_by_origin") @mock.patch(M_PATH + "apt.get_remote_versions_for_package") @@ -682,11 +583,18 @@ purge_value, entitlement_factory, ): - m_get_installed_packages.return_value = [1, 2, 3, 4, 5] - def return_alternatives(p, _exclude_origins): - if p % 2: - return [p] + m_packages = [] + for i in range(1, 6): + package = mock.MagicMock() + type(package).name = mock.PropertyMock(return_value=str(i)) + m_packages.append(package) + + m_get_installed_packages.return_value = m_packages + + def return_alternatives(p, exclude_origin): + if not int(p.name) % 2: + return [int(p.name)] return [] m_get_remote_versions.side_effect = return_alternatives @@ -697,35 +605,79 @@ affordances={"series": ["xenial"]}, purge=purge_value, ) - assert entitlement._perform_disable() is True - assert m_remove_apt_config.call_args_list == [ - mock.call(silent=mock.ANY) - ] + assert entitlement._perform_disable(mock.MagicMock()) is True + assert m_remove_apt_config.call_args_list == [mock.call(mock.ANY)] if purge_value: - m_get_installed_packages.call_args_list = [ + assert m_get_installed_packages.call_args_list == [ mock.call("TestOrigin") ] - m_get_remote_versions.call_args_list = [ - mock.call(1, exclude_origin="TestOrigin"), - mock.call(2, exclude_origin="TestOrigin"), - mock.call(3, exclude_origin="TestOrigin"), - mock.call(4, exclude_origin="TestOrigin"), - mock.call(5, exclude_origin="TestOrigin"), + assert m_get_remote_versions.call_args_list == [ + mock.call(m_packages[0], exclude_origin="TestOrigin"), + mock.call(m_packages[1], exclude_origin="TestOrigin"), + mock.call(m_packages[2], exclude_origin="TestOrigin"), + mock.call(m_packages[3], exclude_origin="TestOrigin"), + mock.call(m_packages[4], exclude_origin="TestOrigin"), ] - m_prompt_for_purge.call_args_list = [ - mock.call([1, 3, 5], [(2, 2), (4, 4)]) + assert m_prompt_for_purge.call_args_list == [ + mock.call( + [m_packages[0], m_packages[2], m_packages[4]], + [(m_packages[1], 2), (m_packages[3], 4)], + mock.ANY, + ) + ] + assert m_execute_removal.call_args_list == [ + mock.call([m_packages[0], m_packages[2], m_packages[4]]) ] - m_execute_removal.call_args_list = [mock.call([1, 3, 5])] - m_execute_reinstall.call_args_list = [ - mock.call([(2, 2), (4, 4)]) + assert m_execute_reinstall.call_args_list == [ + mock.call([(m_packages[1], 2), (m_packages[3], 4)]) ] else: - m_get_installed_packages.call_args_list = [] - m_get_remote_versions.call_args_list = [] - m_prompt_for_purge.call_args_list = [] - m_execute_removal.call_args_list = [] - m_execute_reinstall.call_args_list = [] + assert m_get_installed_packages.call_args_list == [] + assert m_get_remote_versions.call_args_list == [] + assert m_prompt_for_purge.call_args_list == [] + assert m_execute_removal.call_args_list == [] + assert m_execute_reinstall.call_args_list == [] + + @pytest.mark.parametrize( + "repo_cls", + ( + (RepoTestEntitlement), + (RepoTestEntitlementRepoWithRemovePackages), + ), + ) + @mock.patch("uaclient.system.should_reboot", return_value=False) + def test_disable_on_can_disable_true_removes_apt_config_and_packages( + self, + _m_should_reboot, + repo_cls, + entitlement_factory, + ): + """When can_disable, disable removes apt config and packages.""" + entitlement = entitlement_factory(repo_cls) + + if isinstance(entitlement, RepoTestEntitlementRepoWithRemovePackages): + mock_remove_packages = mock.patch.object( + entitlement, "remove_packages" + ) + else: + mock_remove_packages = mock.MagicMock() + + with mock.patch.object( + entitlement, "can_disable", return_value=(True, None) + ): + with mock.patch.object( + entitlement, "remove_apt_config" + ) as m_remove_apt_config: + with mock_remove_packages as m_remove_packages: + assert entitlement.disable(mock.MagicMock()) + + assert [mock.call(mock.ANY)] == m_remove_apt_config.call_args_list + + if isinstance(entitlement, RepoTestEntitlementRepoWithRemovePackages): + assert [mock.call()] == m_remove_packages.call_args_list + else: + assert 0 == m_remove_packages.call_count class TestPurge: @@ -749,14 +701,19 @@ purge=True, ) - assert entitlement.purge_kernel_check(self.packages_to_remove) is True + assert ( + entitlement.purge_kernel_check( + self.packages_to_remove, mock.MagicMock() + ) + is True + ) @mock.patch( M_PATH + "system.get_installed_ubuntu_kernels", return_value=[] ) @mock.patch(M_PATH + "system.get_kernel_info") def test_purge_kernel_check_false_when_no_other_kernels( - self, _m_kernel_info, _m_installed_kernels, entitlement_factory, capsys + self, _m_kernel_info, _m_installed_kernels, entitlement_factory ): entitlement = entitlement_factory( RepoTestEntitlement, @@ -764,17 +721,30 @@ purge=True, ) + mock_progress = mock.MagicMock() assert ( - entitlement.purge_kernel_check(self.mock_kernel_package) is False + entitlement.purge_kernel_check( + self.mock_kernel_package, mock_progress + ) + is False + ) + assert ( + mock.call( + "info", + ( + "No other valid Ubuntu kernel was found in the system.\n" + "Removing the package would potentially make the system " + "unbootable.\n" + "Aborting.\n" + ), + ) + in mock_progress.emit.call_args_list ) - out, _err = capsys.readouterr() - assert "No other valid Ubuntu kernel was found in the system" in out @pytest.mark.parametrize("prompt_answer", (True, False)) @pytest.mark.parametrize( "current_kernel", ("1.2.3-4-fake", "2.3.4-5-fake") ) - @mock.patch(M_PATH + "util.prompt_for_confirmation") @mock.patch( M_PATH + "system.get_installed_ubuntu_kernels", return_value=["2.3.4-5-fake"], @@ -784,33 +754,56 @@ self, m_kernel_info, _m_installed_kernels, - m_confirmation, prompt_answer, current_kernel, entitlement_factory, - capsys, ): m_kernel_info.return_value.uname_release = current_kernel - m_confirmation.return_value = prompt_answer entitlement = entitlement_factory( RepoTestEntitlement, affordances={"series": ["xenial"]}, purge=True, ) + mock_progress = mock.MagicMock() + + def fake_emit(name, payload): + if name == "message_operation" and prompt_answer is False: + raise exceptions.PromptDeniedError() + + mock_progress.emit.side_effect = fake_emit + + expect_raises = ( + does_not_raise() + if prompt_answer + else pytest.raises(exceptions.PromptDeniedError) + ) + with expect_raises: + entitlement.purge_kernel_check( + self.mock_kernel_package, mock_progress + ) + assert ( - entitlement.purge_kernel_check(self.mock_kernel_package) - is prompt_answer + mock.call( + "info", + messages.PURGE_KERNEL_REMOVAL.format( + service="Repo Test Class" + ), + ) + in mock_progress.emit.call_args_list ) - - out, _err = capsys.readouterr() - assert "would uninstall the following kernel(s):" in out assert ( - "{} is the current running kernel.".format(current_kernel) in out + mock.call( + "info", + messages.PURGE_CURRENT_KERNEL.format( + kernel_version=current_kernel + ), + ) + in mock_progress.emit.call_args_list ) @pytest.mark.parametrize( - "remove,reinstall,expected_print,expected_prompt", + ["remove", "reinstall", "expected_print", "expected_progress_emits"], ( ( packages_to_remove, @@ -819,45 +812,66 @@ mock.call(["remove1", "remove2"]), mock.call(["reinstall1", "reinstall2"]), ], - [mock.call(messages.PROCEED_YES_NO)], + [ + mock.call("info", messages.WARN_PACKAGES_REMOVAL), + mock.call("info", " remove1 remove2\n"), + mock.call("info", messages.WARN_PACKAGES_REINSTALL), + mock.call("info", " reinstall1 reinstall2\n"), + mock.call( + "message_operation", + [(mock.ANY, {"msg": messages.PROCEED_YES_NO})], + ), + ], ), ( packages_to_remove, [], [mock.call(["remove1", "remove2"])], - [mock.call(messages.PROCEED_YES_NO)], + [ + mock.call("info", messages.WARN_PACKAGES_REMOVAL), + mock.call("info", " remove1 remove2\n"), + mock.call( + "message_operation", + [(mock.ANY, {"msg": messages.PROCEED_YES_NO})], + ), + ], ), ( [], packages_to_reinstall, [mock.call(["reinstall1", "reinstall2"])], - [mock.call(messages.PROCEED_YES_NO)], + [ + mock.call("info", messages.WARN_PACKAGES_REINSTALL), + mock.call("info", " reinstall1 reinstall2\n"), + mock.call( + "message_operation", + [(mock.ANY, {"msg": messages.PROCEED_YES_NO})], + ), + ], ), ([], [], [], []), ), ) - @mock.patch(M_PATH + "util.print_package_list") - @mock.patch(M_PATH + "util.prompt_for_confirmation") def test_prompt_for_purge( self, - m_confirmation, - m_print_packages, remove, reinstall, expected_print, - expected_prompt, + expected_progress_emits, entitlement_factory, ): - m_confirmation.return_value = True entitlement = entitlement_factory( RepoTestEntitlement, affordances={"series": ["xenial"]}, purge=True, ) - assert entitlement.prompt_for_purge(remove, reinstall) is True - assert m_print_packages.call_args_list == expected_print - assert m_confirmation.call_args_list == expected_prompt + mock_progress = mock.MagicMock() + assert ( + entitlement.prompt_for_purge(remove, reinstall, mock_progress) + is True + ) + assert mock_progress.emit.call_args_list == expected_progress_emits @pytest.mark.parametrize( "remove,expected_remove", @@ -948,7 +962,7 @@ entitlement = entitlement_factory(RepoTestEntitlement, directives={}) with pytest.raises(exceptions.MissingAptURLDirective) as excinfo: - entitlement.remove_apt_config() + entitlement.remove_apt_config(mock.MagicMock()) assert "repotest" in str(excinfo.value) @@ -958,7 +972,7 @@ def test_apt_get_update_called( self, m_run_apt_update_command, _m_apt1, _m_apt2, entitlement ): - entitlement.remove_apt_config() + entitlement.remove_apt_config(mock.MagicMock()) assert mock.call() in m_run_apt_update_command.call_args_list @@ -981,7 +995,7 @@ RepoTestEntitlement, affordances={"series": ["xenial"]}, ) - entitlement.remove_apt_config() + entitlement.remove_apt_config(mock.MagicMock()) assert [ mock.call("http://REPOTEST", "xenial") @@ -1018,7 +1032,7 @@ ) assert 1000 == entitlement.repo_pin_priority - entitlement.remove_apt_config() + entitlement.remove_apt_config(mock.MagicMock()) assert [ mock.call("/etc/apt/preferences.d/ubuntu-repotest") ] == m_ensure_file_absent.call_args_list @@ -1064,7 +1078,7 @@ entitlement = entitlement_factory(repo_cls, directives=directives) with pytest.raises(exc_cls) as excinfo: - entitlement.setup_apt_config() + entitlement.setup_apt_config(mock.MagicMock()) assert err_msg in str(excinfo.value) @pytest.mark.parametrize("enable_by_default", (True, False)) @@ -1092,7 +1106,7 @@ # Drop resourceTokens values from base machine-token. machine_token["resourceTokens"] = [] entitlement.cfg.machine_token_file.write(machine_token) - entitlement.setup_apt_config() + entitlement.setup_apt_config(mock.MagicMock()) expected_msg = ( "No resourceToken present in contract for service Repo Test" " Class. Using machine token as credentials" @@ -1119,7 +1133,7 @@ entitlement, ): """Calls apt.setup_apt_proxy()""" - entitlement.setup_apt_config() + entitlement.setup_apt_config(mock.MagicMock()) assert [ mock.call(http_proxy=None, https_proxy=None, proxy_scope=None) ] == m_setup_apt_proxy.call_args_list @@ -1143,7 +1157,7 @@ """ with mock.patch(M_PATH + "exists") as m_exists: m_exists.return_value = False - entitlement.setup_apt_config() + entitlement.setup_apt_config(mock.MagicMock()) assert [ mock.call("/usr/lib/apt/methods/https"), mock.call("/usr/sbin/update-ca-certificates"), @@ -1164,7 +1178,7 @@ RepoTestEntitlementRepoWithPin, affordances={"series": ["xenial"]} ) with pytest.raises(exceptions.UbuntuProError) as excinfo: - entitlement.setup_apt_config() + entitlement.setup_apt_config(mock.MagicMock()) assert ( "Cannot setup apt pin. Empty apt repo origin value for repotest" in str(excinfo.value) @@ -1196,7 +1210,7 @@ entitlement.origin = "RepoTestOrigin" # don't error on origin = None with mock.patch(M_PATH + "exists") as m_exists: m_exists.return_value = True # Skip prerequisite pkg installs - entitlement.setup_apt_config() + entitlement.setup_apt_config(mock.MagicMock()) assert [ mock.call("/usr/lib/apt/methods/https"), mock.call("/usr/sbin/update-ca-certificates"), @@ -1270,7 +1284,10 @@ """Report ENABLED when apt-policy lists specific aptURL.""" entitlement = entitlement_factory( RepoTestEntitlement, - directives={"aptURL": "https://esm.ubuntu.com"}, + directives={ + "aptURL": "https://esm.ubuntu.com", + "suites": ["bionic-updates", "bionic-security"], + }, ) policy_lines = [ @@ -1320,6 +1337,6 @@ def test_handle_message_operations_for_strings_and_callables( self, msg_ops, retval, output, capsys ): - assert retval is util.handle_message_operations(msg_ops) + assert retval is util.handle_message_operations(msg_ops, print) out, _err = capsys.readouterr() assert output == out diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/exceptions.py ubuntu-advantage-tools-32~16.04/uaclient/exceptions.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/exceptions.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/exceptions.py 2024-04-23 13:37:02.000000000 +0000 @@ -248,6 +248,10 @@ ############################################################################### +class ContractExpiredError(UbuntuProError): + _msg = messages.E_CONTRACT_EXPIRED + + class InvalidServiceOpError(UbuntuProError): _formatted_msg = messages.E_INVALID_SERVICE_OP_FAILURE @@ -335,7 +339,6 @@ class EntitlementsNotEnabledError(UbuntuProError): - exit_code = 4 _msg = messages.E_ENTITLEMENTS_NOT_ENABLED_ERROR @@ -351,6 +354,38 @@ ) +class EntitlementNotEnabledError(UbuntuProError): + _formatted_msg = messages.E_ENTITLEMENT_NOT_ENABLED_ERROR + + def __init__(self, service: str, reason: messages.NamedMessage): + super().__init__( + service=service, + additional_info={ + "reason": { + "code": reason.name, + "title": reason.msg, + "additional_info": reason.additional_info, + } + }, + ) + + +class EntitlementNotDisabledError(UbuntuProError): + _formatted_msg = messages.E_ENTITLEMENT_NOT_DISABLED_ERROR + + def __init__(self, service: str, reason: messages.NamedMessage): + super().__init__( + service=service, + additional_info={ + "reason": { + "code": reason.name, + "title": reason.msg, + "additional_info": reason.additional_info, + } + }, + ) + + class AttachFailureDefaultServices(EntitlementsNotEnabledError): _msg = messages.E_ATTACH_FAILURE_DEFAULT_SERVICES @@ -375,6 +410,30 @@ _formatted_msg = messages.E_INVALID_CONTRACT_DELTAS_SERVICE_TYPE +class EntitlementsAPTDirectivesAreNotUnique(UbuntuProError): + _formatted_msg = messages.E_ENTITLEMENTS_APT_DIRECTIVES_ARE_NOT_UNIQUE + + +class RequiredServiceStopsEnable(UbuntuProError): + _formatted_msg = messages.E_REQUIRED_SERVICE_STOPS_ENABLE + + +class IncompatibleServiceStopsEnable(UbuntuProError): + _formatted_msg = messages.E_INCOMPATIBLE_SERVICE_STOPS_ENABLE + + +class DependentServiceStopsDisable(UbuntuProError): + _formatted_msg = messages.E_DEPENDENT_SERVICE_STOPS_DISABLE + + +class LandscapeConfigFailed(UbuntuProError): + _msg = messages.E_LANDSCAPE_CONFIG_FAILED + + +class NonInteractiveKernelPurgeDisallowed(UbuntuProError): + _msg = messages.E_NON_INTERACTIVE_KERNEL_PURGE_DISALLOWED + + ############################################################################### # CLOUD PRO # ############################################################################### @@ -492,6 +551,10 @@ _formatted_msg = messages.E_CLI_VALID_CHOICES +class EmptyConfigValue(UbuntuProError): + _formatted_msg = messages.E_CLI_EMPTY_CONFIG_VALUE + + class GenericInvalidFormat(UbuntuProError): _formatted_msg = messages.E_CLI_EXPECTED_FORMAT @@ -520,6 +583,10 @@ _msg = messages.E_API_ERROR_ARGS_AND_DATA_TOGETHER +class PromptDeniedError(UbuntuProError): + _msg = messages.E_PROMPT_DENIED + + ############################################################################### # MISCELLANEOUS # ############################################################################### diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/__init__.py ubuntu-advantage-tools-32~16.04/uaclient/files/__init__.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/__init__.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/files/__init__.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,9 +1,11 @@ from uaclient.files.data_types import DataObjectFile, DataObjectFileFormat from uaclient.files.files import MachineTokenFile, UAFile +from uaclient.files.user_config_file import UserConfigFileObject __all__ = [ "DataObjectFile", "DataObjectFileFormat", "MachineTokenFile", "UAFile", + "UserConfigFileObject", ] diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/data_types.py ubuntu-advantage-tools-32~16.04/uaclient/files/data_types.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/data_types.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/files/data_types.py 2024-04-23 13:37:02.000000000 +0000 @@ -80,3 +80,7 @@ def delete(self): self.ua_file.delete() + + @property + def path(self): + return self.ua_file.path diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/files.py ubuntu-advantage-tools-32~16.04/uaclient/files/files.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/files.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/files/files.py 2024-04-23 13:37:02.000000000 +0000 @@ -4,7 +4,14 @@ from datetime import datetime from typing import Any, Dict, Optional # noqa: F401 -from uaclient import defaults, event_logger, exceptions, system, util +from uaclient import ( + defaults, + event_logger, + exceptions, + secret_manager, + system, + util, +) from uaclient.contract_data_types import PublicMachineTokenData event = event_logger.get_event_logger() @@ -64,6 +71,39 @@ system.ensure_file_absent(self.path) +class ProJSONFile: + def __init__( + self, + pro_file: UAFile, + ): + self.pro_file = pro_file + + def write(self, content: Dict[str, Any]): + self.pro_file.write( + content=json.dumps(content, cls=util.DatetimeAwareJSONEncoder) + ) + + def read(self) -> Optional[Dict[str, Any]]: + content = self.pro_file.read() + + if content: + try: + return json.loads(content, cls=util.DatetimeAwareJSONDecoder) + except json.JSONDecodeError as e: + raise exceptions.InvalidJson( + source=self.pro_file.path, out="\n" + str(e) + ) + + return None + + def delete(self): + return self.pro_file.delete() + + @property + def is_present(self): + return self.pro_file.is_present + + class UserCacheFile(UAFile): def __init__(self, name: str): super().__init__( @@ -78,32 +118,30 @@ machine_token_overlay_path: Optional[str] = None, ): file_name = defaults.MACHINE_TOKEN_FILE - self.private_file = UAFile( - file_name, directory + "/" + defaults.PRIVATE_SUBDIR + self.private_file = ProJSONFile( + pro_file=UAFile( + file_name, os.path.join(directory, defaults.PRIVATE_SUBDIR) + ), + ) + self.public_file = ProJSONFile( + pro_file=UAFile(file_name, directory, False) ) - self.public_file = UAFile(file_name, directory, False) self.machine_token_overlay_path = machine_token_overlay_path self._machine_token = None # type: Optional[Dict[str, Any]] self._entitlements = None self._contract_expiry_datetime = None - def write(self, private_content: dict): + def write(self, private_content: Dict[str, Any]): """Update the machine_token file for both pub/private files""" if util.we_are_currently_root(): - private_content_str = json.dumps( - private_content, cls=util.DatetimeAwareJSONEncoder - ) - self.private_file.write(private_content_str) + self.private_file.write(private_content) # PublicMachineTokenData only has public fields defined and # ignores all other (private) fields in from_dict public_content = PublicMachineTokenData.from_dict( private_content ).to_dict(keep_none=False) - public_content_str = json.dumps( - public_content, cls=util.DatetimeAwareJSONEncoder - ) - self.public_file.write(public_content_str) + self.public_file.write(public_content) self._machine_token = None self._entitlements = None @@ -129,13 +167,11 @@ else: file_handler = self.public_file content = file_handler.read() - if not content: - return None - try: - content = json.loads(content, cls=util.DatetimeAwareJSONDecoder) - except Exception: - pass - return content # type: ignore + if content: + secret_manager.secrets.add_secret(content.get("machineToken", "")) + for token in content.get("resourceTokens", []): + secret_manager.secrets.add_secret(token.get("token", "")) + return content @property def is_present(self): diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/notices.py ubuntu-advantage-tools-32~16.04/uaclient/files/notices.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/notices.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/files/notices.py 2024-04-23 13:37:02.000000000 +0000 @@ -14,6 +14,12 @@ class Notice(NoticeFileDetails, Enum): + CONTRACT_EXPIRED = NoticeFileDetails( + label="contract_expired", + order_id="5", + is_permanent=True, + message=messages.CONTRACT_EXPIRED, + ) REBOOT_REQUIRED = NoticeFileDetails( label="reboot_required", order_id="10", @@ -160,6 +166,45 @@ ) system.ensure_file_absent(os.path.join(directory, filename)) + def _get_notice_file_names(self, directory: str) -> List[str]: + """Gets the list of notice file names in the given directory. + + :param directory: The directory to search for notice files. + :returns: List of notice file names. + """ + return [ + file_name + for file_name in os.listdir(directory) + if os.path.isfile(os.path.join(directory, file_name)) + and self._is_valid_notice_file(directory, file_name) + ] + + def _is_valid_notice_file(self, directory: str, file_name: str) -> bool: + """Checks if the notice file is valid. + + :param file_name: The name of the notice file. + :returns: True if the file is valid, False otherwise. + """ + is_permanent_dir = directory == defaults.NOTICES_PERMANENT_DIRECTORY + valid_file_names = { + "{}-{}".format(n.order_id, n.label) + for n in Notice + if n.is_permanent == is_permanent_dir + } + return file_name in valid_file_names + + def _get_default_message(self, file_name: str) -> str: + """Gets the default message for a notice file. + + :param file_name: The name of the notice file. + :returns: The default message defined in the enum. + """ + order_id, label = file_name.split("-") + for notice in Notice: + if notice.order_id == order_id and notice.label == label: + return notice.value.message + return "" + def list(self) -> List[str]: """Gets all the notice files currently saved. @@ -173,35 +218,24 @@ for notice_directory in notice_directories: if not os.path.exists(notice_directory): continue - notice_file_names = [ - file_name - for file_name in os.listdir(notice_directory) - if os.path.isfile(os.path.join(notice_directory, file_name)) - ] + notice_file_names = self._get_notice_file_names(notice_directory) for notice_file_name in notice_file_names: - notice_file_contents = system.load_file( - os.path.join(notice_directory, notice_file_name) - ) + try: + notice_file_contents = system.load_file( + os.path.join(notice_directory, notice_file_name) + ) + except PermissionError: + LOG.warning( + "Permission error while reading " + notice_file_name + ) + continue if notice_file_contents: notices.append(notice_file_contents) else: - # if no contents of file, default to message - # defined in the enum - try: - order_id, label = notice_file_name.split("-") - notice = None - for n in Notice: - if n.order_id == order_id and n.label == label: - notice = n - if notice is None: - raise Exception() - notices.append(notice.value.message) - except Exception: - LOG.warning( - "Something went wrong while processing" - " notice: %s.", - notice_file_name, - ) + default_message = self._get_default_message( + notice_file_name + ) + notices.append(default_message) notices.sort() return notices diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/state_files.py ubuntu-advantage-tools-32~16.04/uaclient/files/state_files.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/state_files.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/files/state_files.py 2024-04-23 13:37:02.000000000 +0000 @@ -12,7 +12,7 @@ data_list, ) from uaclient.files.data_types import DataObjectFile, DataObjectFileFormat -from uaclient.files.files import UAFile, UserCacheFile +from uaclient.files.files import ProJSONFile, UAFile, UserCacheFile SERVICES_ONCE_ENABLED = "services-once-enabled" @@ -174,66 +174,6 @@ file_format=DataObjectFileFormat.JSON, ) - -class UserConfigData(DataObject): - fields = [ - Field("apt_http_proxy", StringDataValue, required=False), - Field("apt_https_proxy", StringDataValue, required=False), - Field("global_apt_http_proxy", StringDataValue, required=False), - Field("global_apt_https_proxy", StringDataValue, required=False), - Field("ua_apt_http_proxy", StringDataValue, required=False), - Field("ua_apt_https_proxy", StringDataValue, required=False), - Field("http_proxy", StringDataValue, required=False), - Field("https_proxy", StringDataValue, required=False), - Field("apt_news", BoolDataValue, required=False), - Field("apt_news_url", StringDataValue, required=False), - Field("poll_for_pro_license", BoolDataValue, required=False), - Field("polling_error_retry_delay", IntDataValue, required=False), - Field("metering_timer", IntDataValue, required=False), - Field("update_messaging_timer", IntDataValue, required=False), - ] - - def __init__( - self, - apt_http_proxy: Optional[str] = None, - apt_https_proxy: Optional[str] = None, - global_apt_http_proxy: Optional[str] = None, - global_apt_https_proxy: Optional[str] = None, - ua_apt_http_proxy: Optional[str] = None, - ua_apt_https_proxy: Optional[str] = None, - http_proxy: Optional[str] = None, - https_proxy: Optional[str] = None, - apt_news: Optional[bool] = None, - apt_news_url: Optional[str] = None, - poll_for_pro_license: Optional[bool] = None, - polling_error_retry_delay: Optional[int] = None, - metering_timer: Optional[int] = None, - update_messaging_timer: Optional[int] = None, - ): - self.apt_http_proxy = apt_http_proxy - self.apt_https_proxy = apt_https_proxy - self.global_apt_http_proxy = global_apt_http_proxy - self.global_apt_https_proxy = global_apt_https_proxy - self.ua_apt_http_proxy = ua_apt_http_proxy - self.ua_apt_https_proxy = ua_apt_https_proxy - self.http_proxy = http_proxy - self.https_proxy = https_proxy - self.apt_news = apt_news - self.apt_news_url = apt_news_url - self.poll_for_pro_license = poll_for_pro_license - self.polling_error_retry_delay = polling_error_retry_delay - self.metering_timer = metering_timer - self.update_messaging_timer = update_messaging_timer - - -user_config_file = DataObjectFile( - UserConfigData, - UAFile("user-config.json", private=True), - DataObjectFileFormat.JSON, - optional_type_errors_become_null=True, -) - - reboot_cmd_marker_file = UAFile("marker-reboot-cmds-required") @@ -283,3 +223,26 @@ UAFile("attachment.json", private=False), DataObjectFileFormat.JSON, ) + + +status_cache_file = ProJSONFile( + pro_file=UAFile( + name="status.json", + private=False, + ) +) + +machine_id_file = UAFile( + "machine-id", + defaults.DEFAULT_PRIVATE_DATA_DIR, + private=True, +) + + +def delete_state_files(): + machine_id_file.delete() + status_cache_file.delete() + attachment_data_file.delete() + anbox_cloud_credentials_file.delete() + reboot_cmd_marker_file.delete() + status_cache_file.delete() diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/tests/test_files.py ubuntu-advantage-tools-32~16.04/uaclient/files/tests/test_files.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/tests/test_files.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/files/tests/test_files.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,4 +1,5 @@ import os +import stat import mock @@ -17,6 +18,17 @@ assert res == file.read() assert res == content + def test_non_private_file_world_readable( + self, + tmpdir, + ): + file = UAFile("test", tmpdir.strpath, False) + file.write("dummy file words") + + assert 0o644 == stat.S_IMODE( + os.lstat(os.path.join(tmpdir.strpath, "test")).st_mode + ) + class TestMachineTokenFile: def test_deleting(self, tmpdir): diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/tests/test_notices.py ubuntu-advantage-tools-32~16.04/uaclient/files/tests/test_notices.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/tests/test_notices.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/files/tests/test_notices.py 2024-04-23 13:37:02.000000000 +0000 @@ -14,8 +14,8 @@ "label,content", ( ( - FakeNotice.a, - "notice_a", + FakeNotice.reboot_script_failed, + "notice_a2", ), ), ) @@ -30,7 +30,10 @@ notice.add(label, content) assert [ mock.call( - os.path.join(defaults.NOTICES_PERMANENT_DIRECTORY, "01-a"), + os.path.join( + defaults.NOTICES_PERMANENT_DIRECTORY, + "12-reboot_script_failed", + ), content, ) ] == sys_write_file.call_args_list @@ -44,15 +47,18 @@ caplog_text, ): notice = NoticesManager() - notice.add(FakeNotice.a, "content") + notice.add(FakeNotice.reboot_required, "content") assert [] == m_sys_write_file.call_args_list - assert "NoticesManager.add(a) called as non-root user" in caplog_text() + assert ( + "NoticesManager.add(reboot_required) called as non-root user" + in caplog_text() + ) @pytest.mark.parametrize( "label,content", ( ( - FakeNotice.a, + FakeNotice.reboot_required, "notice_a", ), ), @@ -74,8 +80,8 @@ "label,content", ( ( - FakeNotice.a, - "notice_a", + FakeNotice.reboot_script_failed, + "notice_a2", ), ), ) @@ -91,7 +97,10 @@ notice.remove(label) assert [ mock.call( - os.path.join(defaults.NOTICES_PERMANENT_DIRECTORY, "01-a"), + os.path.join( + defaults.NOTICES_PERMANENT_DIRECTORY, + "12-reboot_script_failed", + ), ) ] == sys_file_absent.call_args_list @@ -104,10 +113,11 @@ caplog_text, ): notice = NoticesManager() - notice.remove(FakeNotice.a) + notice.remove(FakeNotice.reboot_required) assert [] == m_sys_file_absent.call_args_list assert ( - "NoticesManager.remove(a) called as non-root user" in caplog_text() + "NoticesManager.remove(reboot_required) called as non-root user" + in caplog_text() ) @mock.patch("uaclient.files.notices.NoticesManager.list") @@ -116,11 +126,37 @@ def test_notice_module( self, notice_cls_add, notice_cls_remove, notice_cls_read ): - notices.add(FakeNotice.a) + notices.add(FakeNotice.reboot_required) assert [ - mock.call(FakeNotice.a, "notice_a"), + mock.call(FakeNotice.reboot_required, "notice_a"), ] == notice_cls_add.call_args_list - notices.remove(FakeNotice.a) - assert [mock.call(FakeNotice.a)] == notice_cls_remove.call_args_list + notices.remove(FakeNotice.reboot_required) + assert [ + mock.call(FakeNotice.reboot_required) + ] == notice_cls_remove.call_args_list notices.list() assert 1 == notice_cls_read.call_count + + @mock.patch("uaclient.files.notices.NoticesManager._get_notice_file_names") + def test_get_notice_file_names(self, m_get_notice_file_names): + notice = NoticesManager() + m_get_notice_file_names.return_value = [] + assert [] == notice._get_notice_file_names("directory") + m_get_notice_file_names.return_value = ["file1", "file2", "file3"] + assert ["file1", "file2", "file3"] == notice._get_notice_file_names( + "directory" + ) + + @mock.patch("uaclient.files.notices.NoticesManager._get_notice_file_names") + @mock.patch("uaclient.system.load_file") + def test_list(self, m_load_file, m_get_notice_file_names): + notice = NoticesManager() + + m_get_notice_file_names.side_effect = lambda directory: ( + [] + if directory == defaults.NOTICES_TEMPORARY_DIRECTORY + else ["fakeNotice1"] + ) + m_load_file.return_value = "test" + + assert ["test"] == notice.list() diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/user_config_file.py ubuntu-advantage-tools-32~16.04/uaclient/files/user_config_file.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/files/user_config_file.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/files/user_config_file.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,145 @@ +import copy +import logging +import os +from typing import Optional +from urllib.parse import urlparse + +from uaclient import defaults, event_logger, util +from uaclient.data_types import ( + BoolDataValue, + DataObject, + Field, + IntDataValue, + StringDataValue, +) +from uaclient.files.data_types import DataObjectFile, DataObjectFileFormat +from uaclient.files.files import UAFile + +# Config proxy fields that are visible and configurable +PROXY_FIELDS = [ + "apt_http_proxy", + "apt_https_proxy", + "global_apt_http_proxy", + "global_apt_https_proxy", + "ua_apt_http_proxy", + "ua_apt_https_proxy", + "http_proxy", + "https_proxy", +] + + +class UserConfigData(DataObject): + fields = [ + Field("apt_http_proxy", StringDataValue, required=False), + Field("apt_https_proxy", StringDataValue, required=False), + Field("global_apt_http_proxy", StringDataValue, required=False), + Field("global_apt_https_proxy", StringDataValue, required=False), + Field("ua_apt_http_proxy", StringDataValue, required=False), + Field("ua_apt_https_proxy", StringDataValue, required=False), + Field("http_proxy", StringDataValue, required=False), + Field("https_proxy", StringDataValue, required=False), + Field("apt_news", BoolDataValue, required=False), + Field("apt_news_url", StringDataValue, required=False), + Field("poll_for_pro_license", BoolDataValue, required=False), + Field("polling_error_retry_delay", IntDataValue, required=False), + Field("metering_timer", IntDataValue, required=False), + Field("update_messaging_timer", IntDataValue, required=False), + ] + + def __init__( + self, + apt_http_proxy: Optional[str] = None, + apt_https_proxy: Optional[str] = None, + global_apt_http_proxy: Optional[str] = None, + global_apt_https_proxy: Optional[str] = None, + ua_apt_http_proxy: Optional[str] = None, + ua_apt_https_proxy: Optional[str] = None, + http_proxy: Optional[str] = None, + https_proxy: Optional[str] = None, + apt_news: Optional[bool] = None, + apt_news_url: Optional[str] = None, + poll_for_pro_license: Optional[bool] = None, + polling_error_retry_delay: Optional[int] = None, + metering_timer: Optional[int] = None, + update_messaging_timer: Optional[int] = None, + ): + self.apt_http_proxy = apt_http_proxy + self.apt_https_proxy = apt_https_proxy + self.global_apt_http_proxy = global_apt_http_proxy + self.global_apt_https_proxy = global_apt_https_proxy + self.ua_apt_http_proxy = ua_apt_http_proxy + self.ua_apt_https_proxy = ua_apt_https_proxy + self.http_proxy = http_proxy + self.https_proxy = https_proxy + self.apt_news = apt_news + self.apt_news_url = apt_news_url + self.poll_for_pro_license = poll_for_pro_license + self.polling_error_retry_delay = polling_error_retry_delay + self.metering_timer = metering_timer + self.update_messaging_timer = update_messaging_timer + + +event = event_logger.get_event_logger() +LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) + + +class UserConfigFileObject: + def __init__(self, directory: str = defaults.DEFAULT_DATA_DIR): + file_name = defaults.USER_CONFIG_FILE + self._private = DataObjectFile( + UserConfigData, + UAFile( + file_name, + os.path.join(directory, defaults.PRIVATE_SUBDIR), + private=True, + ), + DataObjectFileFormat.JSON, + optional_type_errors_become_null=True, + ) + self._public = DataObjectFile( + UserConfigData, + UAFile(file_name, directory, private=False), + DataObjectFileFormat.JSON, + optional_type_errors_become_null=True, + ) + + @property + def public_config(self) -> UserConfigData: + public_config = self._public.read() + if public_config is None: + public_config = UserConfigData() + return public_config + + def redact_config_data( + self, user_config: UserConfigData + ) -> UserConfigData: + redacted_data = copy.deepcopy(user_config) + for field in PROXY_FIELDS: + value = getattr(redacted_data, field) + if value: + parsed_url = urlparse(value) + if parsed_url.username or parsed_url.password: + setattr( + redacted_data, + field, + "", + ) + return redacted_data + + def read(self) -> UserConfigData: + if util.we_are_currently_root(): + private_config = self._private.read() + if private_config is not None: + return private_config + public_config = self._public.read() + if public_config is not None: + return public_config + return UserConfigData() + + def write(self, content: UserConfigData): + self._private.write(content) + redacted_content = self.redact_config_data(content) + self._public.write(redacted_content) + + +user_config = UserConfigFileObject(defaults.DEFAULT_DATA_DIR) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/http/__init__.py ubuntu-advantage-tools-32~16.04/uaclient/http/__init__.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/http/__init__.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/http/__init__.py 2024-04-23 13:37:02.000000000 +0000 @@ -218,11 +218,7 @@ code = error.args[0] if len(error.args) > 1: msg = error.args[1] - if ( - code == authentication_error_code - and msg - and "HTTP code 407 from proxy" in msg - ): + if code == authentication_error_code and msg and "407" in msg: raise exceptions.ProxyAuthenticationFailed() elif code == ca_certificates_error_code: raise exceptions.PycurlCACertificatesError(url=url) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/http/tests/test_serviceclient.py ubuntu-advantage-tools-32~16.04/uaclient/http/tests/test_serviceclient.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/http/tests/test_serviceclient.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/http/tests/test_serviceclient.py 2024-04-23 13:37:02.000000000 +0000 @@ -130,10 +130,14 @@ code=response["code"], headers=response.get("headers", {}), body=json.dumps(response["response"], sort_keys=True), - json_dict=response["response"] - if isinstance(response["response"], dict) - else {}, - json_list=response["response"] - if isinstance(response["response"], list) - else [], + json_dict=( + response["response"] + if isinstance(response["response"], dict) + else {} + ), + json_list=( + response["response"] + if isinstance(response["response"], list) + else [] + ), ) == client._get_fake_responses(url) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/livepatch.py ubuntu-advantage-tools-32~16.04/uaclient/livepatch.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/livepatch.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/livepatch.py 2024-04-23 13:37:02.000000000 +0000 @@ -201,9 +201,9 @@ "flavour": flavor, "architecture": arch, "codename": codename, - "build-date": build_date.isoformat() - if build_date is not None - else "unknown", + "build-date": ( + build_date.isoformat() if build_date is not None else "unknown" + ), } headers = self.headers() try: diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/lock.py ubuntu-advantage-tools-32~16.04/uaclient/lock.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/lock.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/lock.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,24 +1,80 @@ -import functools import logging import os import time +from typing import Tuple -from uaclient import config, exceptions, util +from uaclient import exceptions, system, util +from uaclient.data_types import DataObject, Field, StringDataValue from uaclient.files import notices +from uaclient.files.data_types import DataObjectFile, DataObjectFileFormat +from uaclient.files.files import UAFile from uaclient.files.notices import Notice LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) -# Set a module-level callable here so we don't have to reinstantiate -# UAConfig in order to determine dynamic data_path exception handling of -# main_error_handler -clear_lock_file = None + +class LockData(DataObject): + fields = [ + Field("lock_pid", StringDataValue), + Field("lock_holder", StringDataValue), + ] + + def __init__(self, lock_pid: str, lock_holder: str): + self.lock_pid = lock_pid + self.lock_holder = lock_holder + + +lock_data_file = DataObjectFile( + LockData, + UAFile("lock", private=False), + DataObjectFileFormat.JSON, +) + + +def check_lock_info() -> Tuple[int, str]: + """Return lock info if lock file is present the lock is active. + + If process claiming the lock is no longer present, remove the lock file + and log a warning. + + :return: A tuple (pid, string describing lock holder) + If no active lock, pid will be -1. + """ + + try: + lock_data_obj = lock_data_file.read() + except exceptions.InvalidFileFormatError: + raise exceptions.InvalidLockFile(lock_file_path=lock_data_file.path) + + no_lock = (-1, "") + if not lock_data_obj: + return no_lock + + lock_pid = lock_data_obj.lock_pid + lock_holder = lock_data_obj.lock_holder + + try: + system.subp(["ps", lock_pid]) + return (int(lock_pid), lock_holder) + except exceptions.ProcessExecutionError: + if not util.we_are_currently_root(): + LOG.debug( + "Found stale lock file previously held by %s:%s", + lock_pid, + lock_holder, + ) + return (int(lock_pid), lock_holder) + LOG.warning( + "Removing stale lock file previously held by %s:%s", + lock_pid, + lock_holder, + ) + system.ensure_file_absent(lock_data_file.path) + return no_lock def clear_lock_file_if_present(): - global clear_lock_file - if clear_lock_file: - clear_lock_file() + lock_data_file.delete() class RetryLock: @@ -45,33 +101,29 @@ def __init__( self, *_args, - cfg: config.UAConfig, lock_holder: str, sleep_time: int = 10, max_retries: int = 12 ): - self.cfg = cfg self.lock_holder = lock_holder self.sleep_time = sleep_time self.max_retries = max_retries def grab_lock(self): - global clear_lock_file - (lock_pid, cur_lock_holder) = self.cfg.check_lock_info() + (lock_pid, cur_lock_holder) = check_lock_info() if lock_pid > 0: raise exceptions.LockHeldError( lock_request=self.lock_holder, lock_holder=cur_lock_holder, pid=lock_pid, ) - self.cfg.write_cache( - "lock", "{}:{}".format(os.getpid(), self.lock_holder) + lock_data_file.write( + LockData(lock_pid=str(os.getpid()), lock_holder=self.lock_holder) ) notices.add( Notice.OPERATION_IN_PROGRESS, operation=self.lock_holder, ) - clear_lock_file = functools.partial(self.cfg.delete_cache_key, "lock") def __enter__(self): LOG.debug("spin lock starting for %s", self.lock_holder) @@ -91,6 +143,5 @@ time.sleep(self.sleep_time) def __exit__(self, _exc_type, _exc_value, _traceback): - global clear_lock_file - self.cfg.delete_cache_key("lock") - clear_lock_file = None # Unset due to successful lock delete + lock_data_file.delete() + notices.remove(Notice.OPERATION_IN_PROGRESS) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/log.py ubuntu-advantage-tools-32~16.04/uaclient/log.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/log.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/log.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,13 +1,15 @@ import json import logging import os +import pathlib from collections import OrderedDict -from typing import Any, Dict, List # noqa: F401 +from typing import Any, Dict, List, Union # noqa: F401 -from uaclient import defaults, system, util +from uaclient import defaults, secret_manager, system, util +from uaclient.config import UAConfig -class RedactionFilter(logging.Filter): +class RegexRedactionFilter(logging.Filter): """A logging filter to redact confidential info""" def filter(self, record: logging.LogRecord): @@ -15,6 +17,14 @@ return True +class KnownSecretRedactionFilter(logging.Filter): + """A logging filter to redact confidential info""" + + def filter(self, record: logging.LogRecord): + record.msg = secret_manager.secrets.redact_secrets(str(record.msg)) + return True + + class JsonArrayFormatter(logging.Formatter): """Json Array Formatter for our logging mechanism Custom made for Pro logging needs @@ -61,9 +71,20 @@ return json.dumps(list(local_log_record.values())) +def get_user_or_root_log_file_path() -> str: + """ + Gets the correct log_file path, + adjusting for whether the user is root or not. + """ + if util.we_are_currently_root(): + return UAConfig().log_file + else: + return get_user_log_file() + + def get_user_log_file() -> str: """Gets the correct user log_file storage location""" - return system.get_user_cache_dir() + "/ubuntu-pro.log" + return os.path.join(system.get_user_cache_dir(), "ubuntu-pro.log") def get_all_user_log_files() -> List[str]: @@ -74,22 +95,57 @@ user_directories = os.listdir("/home") log_files = [] for user_directory in user_directories: - user_path = ( - "/home/" - + user_directory - + "/.cache/" - + defaults.USER_CACHE_SUBDIR - + "/ubuntu-pro.log" + user_path = os.path.join( + "/home", + user_directory, + ".cache", + defaults.USER_CACHE_SUBDIR, + "ubuntu-pro.log", ) if os.path.isfile(user_path): log_files.append(user_path) return log_files -def setup_journald_logging(log_level, logger): - logger.setLevel(log_level) +def setup_journald_logging(): + logger = logging.getLogger("ubuntupro") + logger.setLevel(logging.INFO) console_handler = logging.StreamHandler() console_handler.setFormatter(JsonArrayFormatter()) - console_handler.setLevel(log_level) - console_handler.addFilter(RedactionFilter()) + console_handler.setLevel(logging.INFO) + console_handler.addFilter(RegexRedactionFilter()) + console_handler.addFilter(KnownSecretRedactionFilter()) logger.addHandler(console_handler) + + +def setup_cli_logging(log_level: Union[str, int], log_file: str): + """Setup logging to log_file + + If run as non-root then log_file is replaced with a user-specific log file. + """ + # support lower-case log_level config value + if isinstance(log_level, str): + log_level = log_level.upper() + + # if we are running as non-root, change log file + if not util.we_are_currently_root(): + log_file = get_user_log_file() + + logger = logging.getLogger("ubuntupro") + logger.setLevel(log_level) + + # Clear all handlers, so they are replaced for this logger + logger.handlers = [] + + # Setup file logging + log_file_path = pathlib.Path(log_file) + if not log_file_path.exists(): + log_file_path.parent.mkdir(parents=True, exist_ok=True) + log_file_path.touch(mode=0o640) + file_handler = logging.FileHandler(log_file) + file_handler.setFormatter(JsonArrayFormatter()) + file_handler.setLevel(log_level) + file_handler.addFilter(RegexRedactionFilter()) + file_handler.addFilter(KnownSecretRedactionFilter()) + + logger.addHandler(file_handler) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/messages/__init__.py ubuntu-advantage-tools-32~16.04/uaclient/messages/__init__.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/messages/__init__.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/messages/__init__.py 2024-05-10 17:07:05.000000000 +0000 @@ -109,6 +109,7 @@ ) ) +UNKNOWN_ERROR = t.gettext("an unknown error") ############################################################################### # GENERIC SYSTEM OPERATIONS # @@ -161,7 +162,7 @@ lambda n: t.ngettext( """\ *Your Ubuntu Pro subscription has EXPIRED* -{{pkg_num}} additional security update require Ubuntu Pro with '{{service}}' enabled. +{{pkg_num}} additional security update requires Ubuntu Pro with '{{service}}' enabled. Renew your subscription at {url}""", # noqa: E501 """\ *Your Ubuntu Pro subscription has EXPIRED* @@ -315,6 +316,7 @@ REFRESH_CONTRACT_ENABLE = t.gettext( "One moment, checking your subscription first" ) +ENABLING_TMPL = t.gettext("Enabling {title}") ENABLED_TMPL = t.gettext("{title} enabled") ACCESS_ENABLED_TMPL = t.gettext("{title} access enabled") ENABLE_FAILED = t.gettext("Could not enable {title}.") @@ -338,8 +340,10 @@ """\ A reboot is required to complete {operation}.""" ) +CONFIGURING_APT_ACCESS = t.gettext("Configuring APT access to {service}") # DISABLE +REMOVING_APT_CONFIGURATION = t.gettext("Removing APT access to {title}") DISABLE_FAILED_TMPL = t.gettext("Could not disable {title}.") DEPENDENT_SERVICE = t.gettext( """\ @@ -355,6 +359,9 @@ APT_REMOVING_PREFERENCES_FILE = t.gettext( "Removing apt preferences file: {filename}" ) +PURGING_PACKAGES = t.gettext( + "Uninstalling all packages installed from {title}" +) # Kernel checks for Purge PURGE_EXPERIMENTAL = t.gettext( @@ -411,7 +418,6 @@ ) RETRY_ERROR_DETAIL_CONNECTIVITY_ERROR = t.gettext("a connectivity error") RETRY_ERROR_DETAIL_URL_ERROR_URL = t.gettext("an error while reaching {url}") -RETRY_ERROR_DETAIL_UNKNOWN = t.gettext("an unknown error") # These are related messages but actually occur during a "refresh" DISABLE_DURING_CONTRACT_REFRESH = t.gettext( @@ -882,6 +888,10 @@ CLI_API_DESC = t.gettext("Calls the Client API endpoints.") CLI_API_ENDPOINT = t.gettext("API endpoint to call") +CLI_API_SHOW_PROGRESS = t.gettext( + "For endpoints that support progress updates, show each progress update " + "on a new line in JSON format" +) CLI_API_ARGS = t.gettext( "Options to pass to the API endpoint, formatted as key=value" ) @@ -895,7 +905,7 @@ "Collect logs and relevant system information into a tarball." ) CLI_COLLECT_LOGS_OUTPUT = t.gettext( - "tarball where the logs will be stored. (Defaults to " "./ua_logs.tar.gz)" + "tarball where the logs will be stored. (Defaults to " "./pro_logs.tar.gz)" ) CLI_CONFIG_SHOW_DESC = t.gettext("Show customizable configuration settings") @@ -951,6 +961,16 @@ " also fix related USNs to the target USN." ) +CLI_FIX_FAIL_UPDATING_ESM_CACHE = t.gettext( + "WARNING: Failed to update ESM cache - package availability may be inaccurate" # noqa +) + +CLI_FIX_FAIL_UPDATING_ESM_CACHE_NON_ROOT = t.gettext( + "{bold}WARNING: Unable to update ESM cache when running as non-root,\n" + "please run sudo apt update and try again " + "if packages cannot be found.{end_bold}" +).format(bold=TxtColor.BOLD, end_bold=TxtColor.ENDC) + CLI_SS_DESC = t.gettext( """\ Show security updates for packages in the system, including all @@ -1300,6 +1320,13 @@ ) + PROMPT_YES_NO ) +KERNEL_DOWNGRADE_WARNING = t.gettext( + """\ +This will downgrade the kernel from {current_version} to {new_version}. +Warning: Downgrading the kernel may cause hardware failures. Please ensure the + hardware is compatible with the new kernel version before proceeding. +""" +) FIPS_SYSTEM_REBOOT_REQUIRED = t.gettext( "FIPS support requires system reboot to complete configuration." ) @@ -1404,6 +1431,8 @@ LIVEPATCH_LTS_REBOOT_REQUIRED = t.gettext( "Livepatch support requires a system reboot across LTS upgrade." ) +INSTALLING_LIVEPATCH = t.gettext("Installing Livepatch") +SETTING_UP_LIVEPATCH = t.gettext("Setting up Livepatch") REALTIME_TITLE = t.gettext("Real-time kernel") REALTIME_DESCRIPTION = t.gettext( @@ -1547,13 +1576,13 @@ the --access-only flag.""", ) -UNEXPECTED_ERROR = NamedMessage( +UNEXPECTED_ERROR = FormattedNamedMessage( "unexpected-error", t.gettext( """\ -Unexpected error(s) occurred. -For more details, see the log: /var/log/ubuntu-advantage.log -To file a bug run: ubuntu-bug ubuntu-advantage-tools""" +An unexpected error occurred: {error_msg} +For more details, see the log: {log_path} +If you think this is a bug, please run: ubuntu-bug ubuntu-advantage-tools""" ), ) @@ -1607,14 +1636,6 @@ Cannot disable dependent service: {required_service}{error}""" ), ) -DEPENDENT_SERVICE_STOPS_DISABLE = FormattedNamedMessage( - "depedent-service-stops-disable", - t.gettext( - """\ -Cannot disable {service_being_disabled} when {dependent_service} is enabled. -""" - ), -) REPO_PURGE_FAIL_NO_ORIGIN = FormattedNamedMessage( "repo-purge-fail-no-origin", t.gettext( @@ -1627,22 +1648,6 @@ "error-enabling-required-service", t.gettext("Cannot enable required service: {service}{error}"), ) -REQUIRED_SERVICE_STOPS_ENABLE = FormattedNamedMessage( - "required-service-stops-enable", - t.gettext( - """\ -Cannot enable {service_being_enabled} when {required_service} is disabled. -""" - ), -) -INCOMPATIBLE_SERVICE_STOPS_ENABLE = FormattedNamedMessage( - "incompatible-service-stops-enable", - t.gettext( - """\ -Cannot enable {service_being_enabled} when \ -{incompatible_service} is enabled.""" - ), -) SERVICE_ERROR_INSTALL_ON_CONTAINER = FormattedNamedMessage( "service-error-install-on-container", @@ -1666,11 +1671,16 @@ "no-apt-url-for-service", t.gettext("{title} does not have an aptURL directive"), ) +NO_SUITES_FOR_SERVICE = FormattedNamedMessage( + "no-suites-for-service", + t.gettext("{title} does not have a suites directive"), +) ALREADY_DISABLED = FormattedNamedMessage( "service-already-disabled", t.gettext( """\ -{title} is not currently enabled\nSee: sudo pro status""" +{title} is not currently enabled - nothing to do. +See: sudo pro status""" ), ) CANNOT_DISABLE_NOT_APPLICABLE = FormattedNamedMessage( @@ -1684,7 +1694,8 @@ "service-already-enabled", t.gettext( """\ -{title} is already enabled.\nSee: sudo pro status""" +{title} is already enabled - nothing to do. +See: sudo pro status""" ), ) UNENTITLED = FormattedNamedMessage( @@ -1813,7 +1824,7 @@ """\ The current kernel ({{version}}, {{arch}}) has reached the end of its livepatch support. Supported kernels are listed here: {url} -Either switch to a supported kernel or `pro disable livepatch` to dismiss this warning.""" # noqa: E501 +Either switch to a supported kernel or `sudo pro disable livepatch` to dismiss this warning.""" # noqa: E501 ).format(url=urls.LIVEPATCH_SUPPORTED_KERNELS), ) LIVEPATCH_KERNEL_NOT_SUPPORTED = FormattedNamedMessage( @@ -1822,7 +1833,7 @@ """\ The current kernel ({{version}}, {{arch}}) is not supported by livepatch. Supported kernels are listed here: {url} -Either switch to a supported kernel or `pro disable livepatch` to dismiss this warning.""" # noqa: E501 +Either switch to a supported kernel or `sudo pro disable livepatch` to dismiss this warning.""" # noqa: E501 ).format( url=urls.LIVEPATCH_SUPPORTED_KERNELS ), # noqa: E501 @@ -1870,6 +1881,17 @@ t.gettext("Cannot install Real-time kernel on a container."), ) +ROS_REQUIRES_ESM = NamedMessage( + "ros-requires-esm", + t.gettext("ROS packages assume ESM updates are enabled."), +) +ROS_UPDATES_REQUIRES_ROS = NamedMessage( + "ros-updates-requires-ros", + t.gettext( + "ROS bug-fix updates assume ROS security fix updates are enabled." + ), +) + UNATTENDED_UPGRADES_SYSTEMD_JOB_DISABLED = NamedMessage( "unattended-upgrades-systemd-job-disabled", t.gettext("apt-daily.timer jobs are not running"), @@ -1902,10 +1924,6 @@ "landscape-client is either not installed or installed but disabled." ), ) -LANDSCAPE_CONFIG_FAILED = NamedMessage( - "landscape-config-failed", - t.gettext("""landscape-config command failed"""), -) INVALID_SECURITY_ISSUE = FormattedNamedMessage( "invalid-security-issue", @@ -1918,6 +1936,11 @@ ) +GENERIC_UNKNOWN_ISSUE = NamedMessage( + "unknown-issue", + UNKNOWN_ERROR, +) + ############################################################################### # ERROR MESSAGES # ############################################################################### @@ -2164,8 +2187,9 @@ "valid-service-failure-unattached", t.gettext( """\ -To use '{{valid_service}}' you need an Ubuntu Pro subscription -Personal and community subscriptions are available at no charge +Cannot {{operation}} services when unattached - nothing to do. +To use '{{valid_service}}' you need an Ubuntu Pro subscription. +Personal and community subscriptions are available at no charge. See {url}""" ).format(url=urls.PRO_HOME_PAGE), ) @@ -2187,6 +2211,16 @@ t.gettext("failed to enable some services"), ) +E_ENTITLEMENT_NOT_ENABLED_ERROR = FormattedNamedMessage( + "entitlement-not-enabled", + t.gettext("failed to enable {service}"), +) + +E_ENTITLEMENT_NOT_DISABLED_ERROR = FormattedNamedMessage( + "entitlement-not-disabled", + t.gettext("failed to disable {service}"), +) + E_ATTACH_FAILURE_DEFAULT_SERVICES = NamedMessage( "attach-failure-default-service", t.gettext( @@ -2232,8 +2266,39 @@ t.gettext("Could not determine contract delta service type {orig} {new}"), ) +E_REQUIRED_SERVICE_STOPS_ENABLE = FormattedNamedMessage( + "required-service-stops-enable", + t.gettext( + """\ +Cannot enable {service_being_enabled} when {required_service} is disabled. +""" + ), +) +E_INCOMPATIBLE_SERVICE_STOPS_ENABLE = FormattedNamedMessage( + "incompatible-service-stops-enable", + t.gettext( + """\ +Cannot enable {service_being_enabled} when \ +{incompatible_service} is enabled.""" + ), +) +E_DEPENDENT_SERVICE_STOPS_DISABLE = FormattedNamedMessage( + "depedent-service-stops-disable", + t.gettext( + """\ +Cannot disable {service_being_disabled} when {dependent_service} is enabled. +""" + ), +) + E_INVALID_PRO_IMAGE = FormattedNamedMessage( - name="invalid-pro-image", msg=t.gettext("Error on Pro Image:\n{error_msg}") + name="invalid-pro-image", + msg=t.gettext( + """\ +Failed to identify this image as a valid Ubuntu Pro image. +Details: +{error_msg}""" + ), ) E_CLOUD_METADATA_ERROR = FormattedNamedMessage( @@ -2393,6 +2458,11 @@ "invalid-arg-choice", "\n" + t.gettext("{arg} must be one of: {choices}") ) +E_CLI_EMPTY_CONFIG_VALUE = FormattedNamedMessage( + "empty-value", + t.gettext("Empty value provided for {arg}."), +) + E_CLI_EXPECTED_FORMAT = FormattedNamedMessage( "generic-invalid-format", "\n" + t.gettext("Expected {expected} but found: {actual}"), @@ -2435,6 +2505,11 @@ t.gettext("Cannot provide both --args and --data at the same time"), ) +E_PROMPT_DENIED = NamedMessage( + "prompt-denied", + t.gettext("Operation cancelled by user"), +) + E_LOCK_HELD_ERROR = FormattedNamedMessage( "lock-held-error", t.gettext( @@ -2536,3 +2611,41 @@ E_PYCURL_CA_CERTIFICATES = NamedMessage( "pycurl-ca-certificates-error", "Problem reading SSL CA certificates" ) + +E_UPDATING_ESM_CACHE = FormattedNamedMessage( + "error-updating-esm-cache", + t.gettext("Error updating ESM services cache: {error}"), +) + +E_ENTITLEMENTS_APT_DIRECTIVES_ARE_NOT_UNIQUE = FormattedNamedMessage( + "entitlements-apt-directives-are-not-unique", + t.gettext( + "There is a problem with the resource directives provided by {url}\n" + "These entitlements: {names} are sharing the following directives\n" + " - APT url: {apt_url}\n - Suite: {suite}\n" + "These directives need to be unique for every entitlement." + ), +) + +E_LANDSCAPE_CONFIG_FAILED = NamedMessage( + "landscape-config-failed", + t.gettext("landscape-config command failed"), +) + +E_NON_INTERACTIVE_KERNEL_PURGE_DISALLOWED = NamedMessage( + "non-interactive-kernel-purge-disallowed", + t.gettext( + "You must use the pro command to purge a service that has installed a " + "kernel" + ), +) + +E_NOT_SUPPORTED = NamedMessage( + "not-supported", + t.gettext("The operation is not supported"), +) + +E_CONTRACT_EXPIRED = NamedMessage( + "contract-expired", + CONTRACT_EXPIRED, +) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/secret_manager.py ubuntu-advantage-tools-32~16.04/uaclient/secret_manager.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/secret_manager.py 1970-01-01 00:00:00.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/secret_manager.py 2024-04-23 13:37:02.000000000 +0000 @@ -0,0 +1,26 @@ +from typing import List + + +class SecretManager: + def __init__(self): + self._secrets = [] + + def add_secret(self, secret: str) -> None: + if secret: # Add only non-empty secrets + self._secrets.append(secret) + + @property + def secrets(self) -> List[str]: + return self._secrets + + def clear_secrets(self) -> None: + self._secrets.clear() + + def redact_secrets(self, log_record: str) -> str: + redacted_record = log_record + for secret in self._secrets: + redacted_record = redacted_record.replace(secret, "") + return redacted_record + + +secrets = SecretManager() diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/security_status.py ubuntu-advantage-tools-32~16.04/uaclient/security_status.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/security_status.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/security_status.py 2024-04-23 13:37:02.000000000 +0000 @@ -7,7 +7,7 @@ import apt_pkg # type: ignore -from uaclient import exceptions, livepatch, messages +from uaclient import exceptions, livepatch, messages, util from uaclient.api.u.pro.security.status.reboot_required.v1 import ( _reboot_required, ) @@ -32,7 +32,6 @@ is_current_series_lts, is_supported, ) -from uaclient.util import print_package_list ESM_SERVICES = ("esm-infra", "esm-apps") @@ -45,6 +44,10 @@ UNAVAILABLE = "upgrade_unavailable" +def print_package_list(packages): + print(util.create_package_list_str(packages)) + + @lru_cache(maxsize=None) def get_origin_information_to_service_map(): series = get_release_info().series @@ -57,9 +60,9 @@ } -def get_installed_packages_by_origin() -> DefaultDict[ - "str", List[apt_pkg.Package] -]: +def get_installed_packages_by_origin() -> ( + DefaultDict["str", List[apt_pkg.Package]] +): result = defaultdict(list) with PreserveAptCfg(get_apt_pkg_cache) as cache: diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/snap.py ubuntu-advantage-tools-32~16.04/uaclient/snap.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/snap.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/snap.py 2024-04-23 13:37:02.000000000 +0000 @@ -5,7 +5,7 @@ import socket from typing import List, NamedTuple, Optional -from uaclient import apt, event_logger, exceptions, messages, system, util +from uaclient import api, apt, event_logger, exceptions, messages, system, util SNAP_CMD = "/usr/bin/snap" SNAP_INSTALL_RETRIES = [0.5, 1.0, 5.0] @@ -143,7 +143,7 @@ raise exceptions.CannotInstallSnapdError() -def run_snapd_wait_cmd(): +def run_snapd_wait_cmd(progress: api.ProgressWrapper): try: system.subp([SNAP_CMD, "wait", "system", "seed.loaded"], capture=True) except exceptions.ProcessExecutionError as e: @@ -151,7 +151,7 @@ LOG.warning( "Detected version of snapd that does not have wait command" ) - event.info(messages.SNAPD_DOES_NOT_HAVE_WAIT_CMD) + progress.emit("info", messages.SNAPD_DOES_NOT_HAVE_WAIT_CMD) else: raise diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/status.py ubuntu-advantage-tools-32~16.04/uaclient/status.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/status.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/status.py 2024-04-23 13:37:02.000000000 +0000 @@ -10,6 +10,7 @@ event_logger, exceptions, livepatch, + lock, messages, util, version, @@ -25,7 +26,7 @@ UserFacingConfigStatus, UserFacingStatus, ) -from uaclient.files import notices, state_files +from uaclient.files import notices, state_files, user_config_file from uaclient.files.notices import Notice from uaclient.messages import TxtColor @@ -134,9 +135,11 @@ def _get_blocked_by_services(ent): return [ { - "name": service.entitlement.name - if not service.entitlement.is_variant - else service.entitlement.variant_name, + "name": ( + service.entitlement.name + if not service.entitlement.is_variant + else service.entitlement.variant_name + ), "reason_code": service.named_msg.name, "reason": service.named_msg.msg, } @@ -207,6 +210,8 @@ """Return configuration of attached status as a dictionary.""" notices.remove(Notice.AUTO_ATTACH_RETRY_FULL_NOTICE) notices.remove(Notice.AUTO_ATTACH_RETRY_TOTAL_FAILURE) + if _is_attached(cfg).is_attached_and_contract_valid: + notices.remove(Notice.CONTRACT_EXPIRED) response = copy.deepcopy(DEFAULT_STATUS) machineTokenInfo = cfg.machine_token["machineTokenInfo"] @@ -372,7 +377,7 @@ userStatus = UserFacingConfigStatus status_val = userStatus.INACTIVE.value status_desc = messages.NO_ACTIVE_OPERATIONS - (lock_pid, lock_holder) = cfg.check_lock_info() + (lock_pid, lock_holder) = lock.check_lock_info() notices_list = notices.list() or [] if lock_pid > 0: status_val = userStatus.ACTIVE.value @@ -394,9 +399,9 @@ "features": cfg.features, } # LP: #2004280 maintain backwards compatibility - ua_config = {} + ua_config = user_config_file.user_config.public_config.to_dict() for key in UA_CONFIGURABLE_KEYS: - if hasattr(cfg, key): + if hasattr(cfg, key) and ua_config[key] is None: ua_config[key] = getattr(cfg, key) ret["config"]["ua_config"] = ua_config @@ -420,7 +425,7 @@ response.update(_get_config_status(cfg)) if util.we_are_currently_root(): - cfg.write_cache("status-cache", response) + state_files.status_cache_file.write(response) response = _handle_beta_resources(cfg, show_all, response) @@ -443,9 +448,13 @@ if entitlement.get("type") == entitlement_name: return { "entitled": "yes" if entitlement.get("entitled") else "no", - "auto_enabled": "yes" - if entitlement.get("obligations", {}).get("enableByDefault") - else "no", + "auto_enabled": ( + "yes" + if entitlement.get("obligations", {}).get( + "enableByDefault" + ) + else "no" + ), "affordances": entitlement.get("affordances", {}), } return {"entitled": "no", "auto_enabled": "no", "affordances": {}} @@ -546,9 +555,9 @@ "description": ent.description, "entitled": entitlement_information["entitled"], "auto_enabled": entitlement_information["auto_enabled"], - "available": "yes" - if ent.name not in inapplicable_resources - else "no", + "available": ( + "yes" if ent.name not in inapplicable_resources else "no" + ), } ) response["services"].sort(key=lambda x: x.get("name", "")) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/system.py ubuntu-advantage-tools-32~16.04/uaclient/system.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/system.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/system.py 2024-04-23 13:37:02.000000000 +0000 @@ -274,6 +274,7 @@ We first check for the machine-id in machine-token.json before looking at the system file. """ + from uaclient.files.state_files import machine_id_file if cfg.machine_token: cfg_machine_id = cfg.machine_token.get("machineTokenInfo", {}).get( @@ -282,15 +283,19 @@ if cfg_machine_id: return cfg_machine_id - fallback_machine_id_file = cfg.data_path("machine-id") + fallback_machine_id = machine_id_file.read() - for path in [ETC_MACHINE_ID, DBUS_MACHINE_ID, fallback_machine_id_file]: + for path in [ETC_MACHINE_ID, DBUS_MACHINE_ID]: if os.path.exists(path): content = load_file(path).rstrip("\n") if content: return content + + if fallback_machine_id: + return fallback_machine_id + machine_id = str(uuid.uuid4()) - cfg.write_cache("machine-id", machine_id) + machine_id_file.write(machine_id) return machine_id @@ -780,9 +785,11 @@ xdg_cache_home = os.environ.get("XDG_CACHE_HOME") if xdg_cache_home: - return xdg_cache_home + "/" + defaults.USER_CACHE_SUBDIR + return os.path.join(xdg_cache_home, defaults.USER_CACHE_SUBDIR) - return os.path.expanduser("~") + "/.cache/" + defaults.USER_CACHE_SUBDIR + return os.path.join( + os.path.expanduser("~"), ".cache", defaults.USER_CACHE_SUBDIR + ) def get_reboot_required_pkgs() -> Optional[RebootRequiredPkgs]: diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_actions.py ubuntu-advantage-tools-32~16.04/uaclient/tests/test_actions.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_actions.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/tests/test_actions.py 2024-04-23 13:37:02.000000000 +0000 @@ -30,13 +30,11 @@ "add_contract_machine_side_effect", "machine_id", "entitlements", - "process_entitlements_delta_side_effect", - "instance_id", + "enable_entitlement_by_name_side_effect", "expected_add_contract_machine_call_args", "expected_machine_token_file_write_call_args", "expected_get_machine_id_call_args", - "expected_config_write_cache_call_args", - "expected_process_entitlements_delta_call_args", + "expected_machine_id_file_call_count", "expected_attachment_data_file_write_call_args", "expected_status_call_args", "expected_update_motd_messages_call_args", @@ -53,12 +51,10 @@ None, None, None, - None, [mock.call(contract_token="token", attachment_dt=mock.ANY)], [], [], - [], - [], + 0, [], [], [], @@ -74,12 +70,10 @@ "get-machine-id-result", mock.sentinel.entitlements, exceptions.ConnectivityError(cause=Exception(), url="url"), - None, [mock.call(contract_token="token", attachment_dt=mock.ANY)], [mock.call({"machineTokenInfo": {"machineId": "machine-id"}})], [mock.call(mock.ANY)], - [mock.call("machine-id", "machine-id")], - [mock.call(mock.ANY, {}, mock.sentinel.entitlements, True)], + 1, [mock.call(mock.ANY)], [mock.call(cfg=mock.ANY)], [mock.call(mock.ANY)], @@ -95,12 +89,10 @@ "get-machine-id-result", mock.sentinel.entitlements, fakes.FakeUbuntuProError(), - None, [mock.call(contract_token="token", attachment_dt=mock.ANY)], [mock.call({"machineTokenInfo": {"machineId": "machine-id"}})], [mock.call(mock.ANY)], - [mock.call("machine-id", "machine-id")], - [mock.call(mock.ANY, {}, mock.sentinel.entitlements, True)], + 1, [mock.call(mock.ANY)], [mock.call(cfg=mock.ANY)], [mock.call(mock.ANY)], @@ -115,13 +107,11 @@ [{"machineTokenInfo": {"machineId": "machine-id"}}], "get-machine-id-result", mock.sentinel.entitlements, - None, - None, + [(True, None)], [mock.call(contract_token="token", attachment_dt=mock.ANY)], [mock.call({"machineTokenInfo": {"machineId": "machine-id"}})], [mock.call(mock.ANY)], - [mock.call("machine-id", "machine-id")], - [mock.call(mock.ANY, {}, mock.sentinel.entitlements, True)], + 1, [mock.call(mock.ANY)], [], [mock.call(mock.ANY)], @@ -136,16 +126,11 @@ [{"machineTokenInfo": {"machineId": "machine-id"}}], "get-machine-id-result", mock.sentinel.entitlements, - None, - "id", + [(True, None)], [mock.call(contract_token="token", attachment_dt=mock.ANY)], [mock.call({"machineTokenInfo": {"machineId": "machine-id"}})], [mock.call(mock.ANY)], - [ - mock.call("machine-id", "machine-id"), - mock.call("instance-id", "id"), - ], - [mock.call(mock.ANY, {}, mock.sentinel.entitlements, True)], + 1, [mock.call(mock.ANY)], [], [mock.call(mock.ANY)], @@ -160,16 +145,11 @@ [{"machineTokenInfo": {"machineId": "machine-id"}}], "get-machine-id-result", mock.sentinel.entitlements, - None, - "id", + [(True, None)], [mock.call(contract_token="token2", attachment_dt=mock.ANY)], [mock.call({"machineTokenInfo": {"machineId": "machine-id"}})], [mock.call(mock.ANY)], - [ - mock.call("machine-id", "machine-id"), - mock.call("instance-id", "id"), - ], - [mock.call(mock.ANY, {}, mock.sentinel.entitlements, False)], + 1, [mock.call(mock.ANY)], [], [mock.call(mock.ANY)], @@ -180,20 +160,24 @@ ), ], ) + @mock.patch( + "uaclient.entitlements.check_entitlement_apt_directives_are_unique", + return_value=(True, None), + ) @mock.patch(M_PATH + "timer.start") - @mock.patch(M_PATH + "identity.get_instance_id", return_value="my-iid") @mock.patch( M_PATH + "contract.UAContractClient.update_activity_token", ) @mock.patch("uaclient.timer.update_messaging.update_motd_messages") @mock.patch(M_PATH + "ua_status.status") + @mock.patch("uaclient.files.state_files.machine_id_file.write") @mock.patch("uaclient.files.state_files.attachment_data_file.write") - @mock.patch(M_PATH + "contract.process_entitlements_delta") + @mock.patch("uaclient.actions.enable_entitlement_by_name") + @mock.patch("uaclient.contract.get_enabled_by_default_services") @mock.patch( "uaclient.files.MachineTokenFile.entitlements", new_callable=mock.PropertyMock, ) - @mock.patch(M_PATH + "config.UAConfig.write_cache") @mock.patch(M_PATH + "system.get_machine_id") @mock.patch("uaclient.files.MachineTokenFile.write") @mock.patch(M_PATH + "contract.UAContractClient.add_contract_machine") @@ -202,27 +186,26 @@ m_add_contract_machine, m_machine_token_file_write, m_get_machine_id, - m_config_write_cache, m_entitlements, - m_process_entitlements_delta, + m_get_enabled_by_default_services, + m_enable_ent_by_name, m_attachment_data_file_write, + m_machine_id_file_write, m_status, m_update_motd_messages, m_update_activity_token, - m_get_instance_id, m_timer_start, + _m_check_ent_apt_directives, token, allow_enable, add_contract_machine_side_effect, machine_id, entitlements, - process_entitlements_delta_side_effect, - instance_id, + enable_entitlement_by_name_side_effect, expected_add_contract_machine_call_args, expected_machine_token_file_write_call_args, expected_get_machine_id_call_args, - expected_config_write_cache_call_args, - expected_process_entitlements_delta_call_args, + expected_machine_id_file_call_count, expected_attachment_data_file_write_call_args, expected_status_call_args, expected_update_motd_messages_call_args, @@ -236,10 +219,13 @@ m_add_contract_machine.side_effect = add_contract_machine_side_effect m_get_machine_id.return_value = machine_id m_entitlements.return_value = entitlements - m_process_entitlements_delta.side_effect = ( - process_entitlements_delta_side_effect + m_enable_ent_by_name.side_effect = ( + enable_entitlement_by_name_side_effect ) - m_get_instance_id.return_value = instance_id + + m_ent1 = mock.MagicMock(variant="") + type(m_ent1).name = mock.PropertyMock(return_value="test1") + m_get_enabled_by_default_services.return_value = [m_ent1] with expected_raises: attach_with_token(cfg, token, allow_enable) @@ -257,12 +243,8 @@ == m_get_machine_id.call_args_list ) assert ( - expected_config_write_cache_call_args - == m_config_write_cache.call_args_list - ) - assert ( - expected_process_entitlements_delta_call_args - == m_process_entitlements_delta.call_args_list + expected_machine_id_file_call_count + == m_machine_token_file_write.call_count ) assert ( expected_attachment_data_file_write_call_args @@ -277,12 +259,33 @@ expected_update_activity_token_call_args == m_update_activity_token.call_args_list ) - assert ( - expected_get_instance_id_call_args - == m_get_instance_id.call_args_list - ) assert expected_timer_start_call_args == m_timer_start.call_args_list + @mock.patch( + M_PATH + "contract.UAContractClient.add_contract_machine", + return_value={}, + ) + @mock.patch( + "uaclient.entitlements.check_entitlement_apt_directives_are_unique", + side_effect=exceptions.EntitlementsAPTDirectivesAreNotUnique( + url="test_url", + names="ent1, ent2", + apt_url="test", + suite="release", + ), + ) + def test_attach_with_token_with_non_unique_entitlement_directives( + self, + _m_check_ent_apt_directives, + _m_add_contract_machine, + ): + with pytest.raises(exceptions.EntitlementsAPTDirectivesAreNotUnique): + attach_with_token( + cfg=mock.MagicMock(), + token="token", + allow_enable=True, + ) + class TestAutoAttach: @mock.patch(M_PATH + "attach_with_token") @@ -309,10 +312,8 @@ M_PATH + "contract.UAContractClient.get_contract_token_for_cloud_instance" # noqa ) - @mock.patch(M_PATH + "identity.get_instance_id", return_value="my-iid") def test_raise_unexpected_errors( self, - _m_get_instance_id, m_get_contract_token_for_cloud_instances, FakeConfig, ): @@ -334,7 +335,9 @@ @mock.patch("uaclient.actions._write_command_output_to_file") class TestCollectLogs: + @mock.patch("uaclient.actions.status") @mock.patch("uaclient.actions.LOG.warning") + @mock.patch("uaclient.util.get_pro_environment") @mock.patch("uaclient.util.we_are_currently_root", return_value=False) @mock.patch("uaclient.system.write_file") @mock.patch("uaclient.system.load_file") @@ -351,10 +354,14 @@ m_load_file, m_write_file, m_we_are_currently_root, + m_env_vars, m_log_warning, + m_status, m_write_cmd, tmpdir, ): + m_env_vars.return_value = {"test": "test"} + m_status.return_value = ({"test": "test"}, 0) log_file = tmpdir.join("user-log").strpath m_get_user.return_value = log_file m_get_state_files.return_value = ["a", "b"] @@ -370,7 +377,7 @@ mock.call("a"), mock.call("b"), ] == m_load_file.call_args_list - assert 3 == m_write_file.call_count + assert 5 == m_write_file.call_count # apparmor checks assert 1 == m_system_subp.call_count @@ -380,6 +387,8 @@ print(m_write_file.call_args_list) assert [ + mock.call("test/pro-status.json", '{"test": "test"}'), + mock.call("test/environment_vars.json", '{"test": "test"}'), mock.call("test/user0.log", "test"), mock.call("test/b", "test"), mock.call("test/apparmor_logs.txt", APPARMOR_DENIED), diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_apt.py ubuntu-advantage-tools-32~16.04/uaclient/tests/test_apt.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_apt.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/tests/test_apt.py 2024-04-23 13:37:02.000000000 +0000 @@ -1158,6 +1158,9 @@ @pytest.mark.parametrize("is_esm", (True, False)) @pytest.mark.parametrize("can_enable_infra", ("yes", "no")) @pytest.mark.parametrize("can_enable_apps", ("yes", "no")) + @mock.patch( + "uaclient.files.state_files.status_cache_file.read", return_value=None + ) @mock.patch("uaclient.entitlements.esm.ESMAppsEntitlement") @mock.patch("uaclient.entitlements.esm.ESMInfraEntitlement") @mock.patch("uaclient.apt.system.is_current_series_lts") @@ -1178,6 +1181,7 @@ m_is_lts, m_infra_entitlement, m_apps_entitlement, + m_status_cache_file_read, is_lts, cache_call_list, apps_status, @@ -1221,11 +1225,9 @@ infra_disable_repo_count = 0 apps_disable_repo_count = 0 status_count = 0 - status_cache_args_list = [] if is_lts: status_count = 1 - status_cache_args_list = [mock.call("status-cache")] if ( apps_status == ApplicationStatus.DISABLED and can_enable_apps == "yes" @@ -1244,10 +1246,9 @@ infra_disable_repo_count = 1 cfg = FakeConfig() - with mock.patch.object(cfg, "read_cache", return_value=None): - update_esm_caches(cfg) - assert cfg.read_cache.call_args_list == status_cache_args_list + update_esm_caches(cfg) + assert status_count == m_status_cache_file_read.call_count assert m_esm_cache.call_args_list == cache_call_list assert ( m_infra.setup_local_esm_repo.call_count == infra_setup_repo_count @@ -1609,10 +1610,12 @@ mock.call( tmpdir + "/var/cache/apt/archives/partial", exist_ok=True, - mode=755, + mode=0o755, ), mock.call( - tmpdir + "/var/lib/apt/lists/partial", exist_ok=True, mode=755 + tmpdir + "/var/lib/apt/lists/partial", + exist_ok=True, + mode=0o755, ), ] diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_apt_news.py ubuntu-advantage-tools-32~16.04/uaclient/tests/test_apt_news.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_apt_news.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/tests/test_apt_news.py 2024-05-10 17:07:05.000000000 +0000 @@ -4,8 +4,8 @@ import pytest from uaclient import apt_news, messages +from uaclient.api.u.pro.status.is_attached.v1 import ContractExpiryStatus from uaclient.clouds.identity import NoCloudTypeReason -from uaclient.contract import ContractExpiryStatus M_PATH = "uaclient.apt_news." @@ -14,29 +14,54 @@ class TestAptNews: @pytest.mark.parametrize( - ["selectors", "series", "cloud_type", "attached", "expected"], + [ + "selectors", + "series", + "cloud_type", + "attached", + "architecture", + "package_version_side_effect", + "expected", + ], [ ( apt_news.AptNewsMessageSelectors(), "xenial", (None, NoCloudTypeReason.NO_CLOUD_DETECTED), False, + None, + None, True, ), ( + apt_news.AptNewsMessageSelectors(codenames=["bionic"]), + "xenial", + (None, NoCloudTypeReason.NO_CLOUD_DETECTED), + False, + None, + None, + False, + ), + ( apt_news.AptNewsMessageSelectors( codenames=["bionic", "xenial"] ), "xenial", (None, NoCloudTypeReason.NO_CLOUD_DETECTED), False, + None, + None, True, ), ( - apt_news.AptNewsMessageSelectors(codenames=["bionic"]), + apt_news.AptNewsMessageSelectors( + codenames=["xenial"], pro=True + ), "xenial", (None, NoCloudTypeReason.NO_CLOUD_DETECTED), False, + None, + None, False, ), ( @@ -45,56 +70,138 @@ ), "xenial", (None, NoCloudTypeReason.NO_CLOUD_DETECTED), + True, + None, + None, + True, + ), + ( + apt_news.AptNewsMessageSelectors( + codenames=["bionic"], + pro=False, + clouds=["gce"], + ), + "bionic", + (None, NoCloudTypeReason.NO_CLOUD_DETECTED), False, + None, + None, False, ), ( apt_news.AptNewsMessageSelectors( - codenames=["xenial"], pro=True + codenames=["bionic"], + pro=False, + clouds=["gce"], + ), + "bionic", + (None, NoCloudTypeReason.CLOUD_ID_ERROR), + False, + None, + None, + False, + ), + ( + apt_news.AptNewsMessageSelectors( + pro=False, architectures=["amd64"] ), "xenial", (None, NoCloudTypeReason.NO_CLOUD_DETECTED), + False, + "amd64", + None, True, + ), + ( + apt_news.AptNewsMessageSelectors( + pro=False, architectures=["arm64"] + ), + "xenial", + (None, NoCloudTypeReason.NO_CLOUD_DETECTED), + False, + "amd64", + None, + False, + ), + ( + apt_news.AptNewsMessageSelectors( + pro=True, packages=[["not-desktop", "==", "1.0.0"]] + ), + "xenial", + (None, NoCloudTypeReason.NO_CLOUD_DETECTED), + True, + None, + ["1.0.0"], True, ), ( apt_news.AptNewsMessageSelectors( - codenames=["bionic"], pro=False + pro=True, packages=[["desktop", "==", "1.0.0"]] ), "xenial", (None, NoCloudTypeReason.NO_CLOUD_DETECTED), + True, + None, + [None], False, + ), + ( + apt_news.AptNewsMessageSelectors( + pro=False, packages=[["not-desktop", "=="]] + ), + "xenial", + (None, NoCloudTypeReason.NO_CLOUD_DETECTED), + False, + None, + ["1.0.0"], False, ), ( apt_news.AptNewsMessageSelectors( - codenames=["bionic"], pro=False + pro=False, packages=[["not-desktop", "==", "1.0.0"]] ), - "bionic", + "xenial", (None, NoCloudTypeReason.NO_CLOUD_DETECTED), False, - True, + None, + ["1.0.1"], + False, ), ( apt_news.AptNewsMessageSelectors( - codenames=["bionic"], - pro=False, - clouds=["gce"], + pro=False, packages=[["not-desktop", ">", "1.0.0"]] ), - "bionic", + "xenial", (None, NoCloudTypeReason.NO_CLOUD_DETECTED), False, + None, + ["1.0.1"], + True, + ), + ( + apt_news.AptNewsMessageSelectors( + pro=False, packages=[["not-desktop", "<", "1.0.0"]] + ), + "xenial", + (None, NoCloudTypeReason.NO_CLOUD_DETECTED), False, + None, + ["0.0.1"], + True, ), ( apt_news.AptNewsMessageSelectors( codenames=["bionic"], pro=False, clouds=["gce"], + architectures=["amd64"], + packages=[["not-desktop", ">", "1.0.0"]], ), "bionic", - (None, NoCloudTypeReason.CLOUD_ID_ERROR), + ("aws", None), False, + "arm4", + ["0.0.7"], False, ), ( @@ -102,35 +209,53 @@ codenames=["bionic"], pro=False, clouds=["gce"], + architectures=["amd64"], + packages=[["not-desktop", ">", "1.0.0"]], ), "bionic", - ("aws", None), - False, + ("gce", None), False, + "amd64", + ["1.0.1"], + True, ), ( apt_news.AptNewsMessageSelectors( codenames=["bionic"], pro=False, clouds=["gce"], + architectures=["amd64"], + packages=[ + ["does-not-match", "<", "4~"], + ["matches", ">", "1.0.0"], + ["not-installed", "<", "1"], + ], ), "bionic", ("gce", None), False, + "amd64", + ["4", "1.0.1", None], True, ), ], ) @mock.patch(M_PATH + "get_cloud_type") @mock.patch(M_PATH + "system.get_release_info") + @mock.patch(M_PATH + "system.get_dpkg_arch") + @mock.patch(M_PATH + "get_pkg_version") def test_do_selectors_apply( self, + m_get_pkg_version, + m_get_dpkg_arch, m_get_platform_info, m_get_cloud_type, selectors, series, cloud_type, attached, + architecture, + package_version_side_effect, expected, FakeConfig, ): @@ -140,6 +265,8 @@ cfg = FakeConfig() m_get_platform_info.return_value = mock.MagicMock(series=series) m_get_cloud_type.return_value = cloud_type + m_get_dpkg_arch.return_value = architecture + m_get_pkg_version.side_effect = package_version_side_effect assert expected == apt_news.do_selectors_apply(cfg, selectors) @pytest.mark.parametrize( @@ -466,50 +593,62 @@ @pytest.mark.parametrize( [ "expiry_status", + "remaining_days", "expected", ], [ ( - (ContractExpiryStatus.ACTIVE, 0), + ContractExpiryStatus.ACTIVE, + 0, None, ), ( - (ContractExpiryStatus.NONE, 0), + ContractExpiryStatus.NONE, + 0, None, ), ( - (ContractExpiryStatus.ACTIVE_EXPIRED_SOON, 10), + ContractExpiryStatus.ACTIVE_EXPIRED_SOON, + 10, messages.CONTRACT_EXPIRES_SOON.pluralize(10).format( remaining_days=10 ), ), ( - (ContractExpiryStatus.ACTIVE_EXPIRED_SOON, 15), + ContractExpiryStatus.ACTIVE_EXPIRED_SOON, + 15, messages.CONTRACT_EXPIRES_SOON.pluralize(15).format( remaining_days=15 ), ), ( - (ContractExpiryStatus.EXPIRED_GRACE_PERIOD, -4), + ContractExpiryStatus.EXPIRED_GRACE_PERIOD, + -4, messages.CONTRACT_EXPIRED_GRACE_PERIOD.pluralize(10).format( remaining_days=10, expired_date="21 Dec 2012" ), ), ( - (ContractExpiryStatus.EXPIRED, -15), + ContractExpiryStatus.EXPIRED, + -15, messages.CONTRACT_EXPIRED, ), ], ) - @mock.patch(M_PATH + "get_contract_expiry_status") + @mock.patch(M_PATH + "_is_attached") def test_local_apt_news( self, - m_get_contract_expiry_status, + m_is_attached, expiry_status, + remaining_days, expected, FakeConfig, ): - m_get_contract_expiry_status.return_value = expiry_status + m_is_attached.return_value = mock.MagicMock( + is_attached=True, + contract_status=expiry_status.value, + contract_remaining_days=remaining_days, + ) cfg = FakeConfig.for_attached_machine( effective_to=datetime(2012, 12, 21, tzinfo=timezone.utc) diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_config.py ubuntu-advantage-tools-32~16.04/uaclient/tests/test_config.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_config.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/tests/test_config.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,9 +1,6 @@ import copy -import datetime -import itertools import json import os -import stat import mock import pytest @@ -12,23 +9,19 @@ from uaclient.config import ( UA_CONFIGURABLE_KEYS, VALID_UA_CONFIG_KEYS, - DataPath, get_config_path, parse_config, ) from uaclient.conftest import FakeNotice -from uaclient.defaults import DEFAULT_CONFIG_FILE, PRIVATE_SUBDIR +from uaclient.defaults import DEFAULT_CONFIG_FILE from uaclient.entitlements import valid_services from uaclient.entitlements.entitlement_status import ApplicationStatus -from uaclient.files import notices +from uaclient.files import notices, user_config_file from uaclient.files.notices import NoticesManager from uaclient.util import depth_first_merge_overlay_dict from uaclient.yaml import safe_dump -KNOWN_DATA_PATHS = ( - ("machine-access-cis", "machine-access-cis.json"), - ("instance-id", "instance-id"), -) +KNOWN_DATA_PATHS = (("machine-id", "machine-id"),) M_PATH = "uaclient.entitlements." @@ -74,13 +67,13 @@ ( ([], ()), ( - [[FakeNotice.a2, "a1"]], + [[FakeNotice.reboot_script_failed, "a1"]], ["a1"], ), ( [ - [FakeNotice.a, "a1"], - [FakeNotice.a2, "a2"], + [FakeNotice.reboot_required, "a1"], + [FakeNotice.reboot_script_failed, "a2"], ], [ "a1", @@ -89,8 +82,8 @@ ), ( [ - [FakeNotice.a, "a1"], - [FakeNotice.a, "a1"], + [FakeNotice.reboot_required, "a1"], + [FakeNotice.reboot_required, "a1"], ], [ "a1", @@ -116,11 +109,11 @@ "_notices", ( ([]), - ([[FakeNotice.a]]), + ([[FakeNotice.reboot_required]]), ( [ - [FakeNotice.a], - [FakeNotice.a2], + [FakeNotice.reboot_required], + [FakeNotice.reboot_script_failed], ] ), ), @@ -139,29 +132,29 @@ @pytest.mark.parametrize( "notices_,removes,expected", ( - ([], [FakeNotice.a], []), + ([], [FakeNotice.reboot_required], []), ( - [[FakeNotice.a2]], - [FakeNotice.a2], + [[FakeNotice.reboot_script_failed]], + [FakeNotice.reboot_script_failed], [], ), ( [ - [FakeNotice.a], - [FakeNotice.a2], + [FakeNotice.reboot_required], + [FakeNotice.reboot_script_failed], ], - [FakeNotice.a], + [FakeNotice.reboot_required], ["notice_a2"], ), ( [ - [FakeNotice.a], - [FakeNotice.a2], - [FakeNotice.b], + [FakeNotice.reboot_required], + [FakeNotice.reboot_script_failed], + [FakeNotice.enable_reboot_required], ], [ - FakeNotice.a, - FakeNotice.a2, + FakeNotice.reboot_required, + FakeNotice.reboot_script_failed, ], ["notice_b"], ), @@ -269,41 +262,6 @@ assert accountInfo == cfg.machine_token_file.account -class TestDataPath: - def test_data_path_returns_data_dir_path_without_key(self, FakeConfig): - """The data_path method returns the data_dir when key is absent.""" - cfg = FakeConfig({"data_dir": "/my/dir"}) - assert "/my/dir/{}".format(PRIVATE_SUBDIR) == cfg.data_path() - - @pytest.mark.parametrize("key,path_basename", KNOWN_DATA_PATHS) - def test_data_path_returns_file_path_with_defined_data_paths( - self, key, path_basename, FakeConfig - ): - """When key is defined in Config.data_paths return data_path value.""" - cfg = FakeConfig({"data_dir": "/my/dir"}) - private_path = "/my/dir/{}/{}".format(PRIVATE_SUBDIR, path_basename) - assert private_path == cfg.data_path(key=key) - - @pytest.mark.parametrize( - "key,path_basename", (("notHere", "notHere"), ("anything", "anything")) - ) - def test_data_path_returns_file_path_with_undefined_data_paths( - self, key, path_basename, FakeConfig - ): - """When key is not in Config.data_paths the key is used to data_dir""" - cfg = FakeConfig({"data_dir": "/my/d"}) - assert "/my/d/{}/{}".format(PRIVATE_SUBDIR, key) == cfg.data_path( - key=key - ) - - def test_data_path_returns_public_path_for_public_datapath( - self, FakeConfig - ): - cfg = FakeConfig({"data_dir": "/my/d"}) - cfg.data_paths["test_path"] = DataPath("test_path", False) - assert "/my/d/test_path" == cfg.data_path("test_path") - - CFG_BASE_CONTENT = """\ # Ubuntu Pro client config file. # If you modify this file, run "pro refresh config" to ensure changes are @@ -354,7 +312,7 @@ class TestUserConfigKeys: @pytest.mark.parametrize("attr_name", UA_CONFIGURABLE_KEYS) - @mock.patch("uaclient.config.state_files.user_config_file.write") + @mock.patch("uaclient.config.user_config_file.user_config.write") def test_user_configurable_keys_set_user_config( self, write, attr_name, tmpdir, FakeConfig ): @@ -368,279 +326,6 @@ assert attr_name + "value" == getattr(cfg.user_config, attr_name) -class TestWriteCache: - @pytest.mark.parametrize( - "key,content", - (("unknownkey", "content1"), ("another-one", "content2")), - ) - def test_write_cache_write_key_name_in_data_dir_when_data_path_absent( - self, tmpdir, FakeConfig, key, content - ): - """When key is not in data_paths, write content to data_dir/key.""" - cfg = FakeConfig() - expected_path = tmpdir.join(PRIVATE_SUBDIR, key) - - assert not expected_path.check(), "Found unexpected file {}".format( - expected_path - ) - assert None is cfg.write_cache(key, content) - assert expected_path.check(), "Missing expected file {}".format( - expected_path - ) - assert content == cfg.read_cache(key) - - def test_write_cache_creates_secure_private_dir(self, tmpdir, FakeConfig): - """private_dir is created with permission 0o700.""" - cfg = FakeConfig() - # unknown keys are written to the private dir - expected_dir = tmpdir.join(PRIVATE_SUBDIR) - assert None is cfg.write_cache("somekey", "somevalue") - assert True is os.path.isdir( - expected_dir.strpath - ), "Missing expected directory {}".format(expected_dir) - assert 0o700 == stat.S_IMODE(os.lstat(expected_dir.strpath).st_mode) - - def test_write_cache_creates_dir_when_data_dir_does_not_exist( - self, tmpdir, FakeConfig - ): - """When data_dir doesn't exist, create it.""" - tmp_subdir = tmpdir.join("does/not/exist") - cfg = FakeConfig({"data_dir": tmp_subdir.strpath}) - - assert False is os.path.isdir( - tmp_subdir.strpath - ), "Found unexpected directory {}".format(tmp_subdir) - assert None is cfg.write_cache("somekey", "someval") - assert True is os.path.isdir( - tmp_subdir.strpath - ), "Missing expected directory {}".format(tmp_subdir) - assert "someval" == cfg.read_cache("somekey") - - @pytest.mark.parametrize( - "key,value", (("dictkey", {"1": "v1"}), ("listkey", [1, 2, 3])) - ) - def test_write_cache_writes_json_string_when_content_not_a_string( - self, tmpdir, FakeConfig, key, value - ): - """When content is not a string, write a json string.""" - cfg = FakeConfig() - - expected_json_content = json.dumps(value) - assert None is cfg.write_cache(key, value) - with open(tmpdir.join(PRIVATE_SUBDIR, key).strpath, "r") as stream: - assert expected_json_content == stream.read() - assert value == cfg.read_cache(key) - - @pytest.mark.parametrize( - "datapath,mode", - ( - (DataPath("path", False), 0o644), - (DataPath("path", True), 0o600), - ), - ) - def test_permissions(self, FakeConfig, datapath, mode): - cfg = FakeConfig() - cfg.data_paths = {"path": datapath} - cfg.write_cache("path", "") - assert mode == stat.S_IMODE(os.lstat(cfg.data_path("path")).st_mode) - - def test_write_datetime(self, FakeConfig): - cfg = FakeConfig() - key = "test_key" - dt = datetime.datetime.now() - cfg.write_cache(key, dt) - with open(cfg.data_path(key)) as f: - assert dt.isoformat() == f.read().strip('"') - - -class TestReadCache: - @pytest.mark.parametrize("key,path_basename", KNOWN_DATA_PATHS) - def test_read_cache_returns_none_when_data_path_absent( - self, tmpdir, FakeConfig, key, path_basename - ): - """Return None when the specified key data_path is not cached.""" - cfg = FakeConfig() - assert None is cfg.read_cache(key) - assert not tmpdir.join(path_basename).check() - - @pytest.mark.parametrize("key,path_basename", KNOWN_DATA_PATHS) - def test_read_cache_returns_content_when_data_path_present( - self, tmpdir, FakeConfig, key, path_basename - ): - cfg = FakeConfig() - os.makedirs(tmpdir.join(PRIVATE_SUBDIR).strpath) - data_path = tmpdir.join(PRIVATE_SUBDIR, path_basename) - with open(data_path.strpath, "w") as f: - f.write("content{}".format(key)) - - assert "content{}".format(key) == cfg.read_cache(key) - - @pytest.mark.parametrize("key,path_basename", KNOWN_DATA_PATHS) - def test_read_cache_returns_stuctured_content_when_json_data_path_present( - self, tmpdir, FakeConfig, key, path_basename - ): - cfg = FakeConfig() - os.makedirs(tmpdir.join(PRIVATE_SUBDIR).strpath) - data_path = tmpdir.join(PRIVATE_SUBDIR, path_basename) - expected = {key: "content{}".format(key)} - with open(data_path.strpath, "w") as f: - f.write(json.dumps(expected)) - - assert expected == cfg.read_cache(key) - - def test_datetimes_are_unserialised(self, tmpdir, FakeConfig): - cfg = FakeConfig() - os.makedirs(tmpdir.join(PRIVATE_SUBDIR).strpath) - data_path = tmpdir.join(PRIVATE_SUBDIR, "dt_test") - with open(data_path.strpath, "w") as f: - f.write('{"dt": "2019-07-25T14:35:51"}') - - actual = cfg.read_cache("dt_test") - assert { - "dt": datetime.datetime( - 2019, 7, 25, 14, 35, 51, tzinfo=datetime.timezone.utc - ) - } == actual - - -class TestDeleteCacheKey: - @pytest.mark.parametrize("property_name", ("status-cache", "lock")) - def test_delete_cache_key_removes_public_or_private_data_path_files( - self, property_name, FakeConfig - ): - cfg = FakeConfig() - cfg.write_cache(property_name, "himom") - assert True is os.path.exists(cfg.data_path(property_name)) - cfg.delete_cache_key(property_name) - assert False is os.path.exists(cfg.data_path(property_name)) - assert None is cfg.read_cache(property_name) - - @pytest.mark.parametrize( - "property_name,clears_cache", - ( - ("machine-token", True), - ("machine-access-cis", True), - ("machine", False), - ), - ) - def test_delete_cache_key_clears_machine_token_and_entitlements( - self, property_name, clears_cache, FakeConfig, all_resources_available - ): - cfg = FakeConfig() - token = { - "availableResources": all_resources_available, - "machineTokenInfo": { - "contractInfo": { - "resourceEntitlements": [ - {"type": "entitlement1", "entitled": True}, - {"type": "entitlement2", "entitled": True}, - ] - } - }, - } - cfg.machine_token_file.write(token) - # sets config _entitlements and _machine_token cache - cfg.machine_token_file.entitlements - assert cfg.machine_token_file._entitlements is not None - assert cfg.machine_token_file._machine_token is not None - if property_name == "machine-token": - cfg.machine_token_file.delete() - else: - cfg.delete_cache_key(property_name) - if clears_cache: - # internal cache is cleared - assert cfg.machine_token_file._entitlements is None - assert cfg.machine_token_file._machine_token is None - - # Reconstitutes _entitlements and _machine_token caches - entitlements = cfg.machine_token_file.entitlements - if property_name == "machine-token": - # We performed delete_cache_key("machine-token") above, so None now - assert None is cfg.machine_token_file._entitlements - assert None is cfg.machine_token - else: - # re-constitute from cache - assert entitlements is cfg.machine_token_file._entitlements - assert cfg.machine_token_file._machine_token is cfg.machine_token - - -class TestDeleteCache: - def test_delete_cache_unsets_entitlements( - self, FakeConfig, all_resources_available - ): - """The delete_cache unsets any cached entitlements content.""" - cfg = FakeConfig() - token = { - "availableResources": all_resources_available, - "machineTokenInfo": { - "contractInfo": { - "resourceEntitlements": [ - {"type": "entitlement1", "entitled": True} - ] - } - }, - } - cfg.machine_token_file.write(token) - previous_entitlements = { - "entitlement1": { - "entitlement": {"type": "entitlement1", "entitled": True} - } - } - assert previous_entitlements == cfg.machine_token_file.entitlements - cfg.delete_cache() - cfg.machine_token_file.delete() - assert {} == cfg.machine_token_file.entitlements - - def test_delete_cache_removes_all_data_path_files( - self, tmpdir, FakeConfig - ): - """Any cached files defined in cfg.data_paths will be removed.""" - cfg = FakeConfig() - # Create half of the cached files, but not all - odd_keys = list(sorted(cfg.data_paths.keys()))[::2] - for odd_key in odd_keys: - if odd_key == "notices": - # notices key expects specific list or lists format - value = [[odd_key, odd_key]] - else: - value = odd_key - cfg.write_cache(odd_key, value) - - present_files = list( - itertools.chain( - *[walk_entry[2] for walk_entry in os.walk(tmpdir.strpath)] - ) - ) - assert len(odd_keys) == len(present_files) - cfg.delete_cache() - dirty_files = list( - itertools.chain( - *[walk_entry[2] for walk_entry in os.walk(tmpdir.strpath)] - ) - ) - assert 0 == len(dirty_files), "{} files not deleted".format( - ", ".join(dirty_files) - ) - - def test_delete_cache_ignores_files_not_defined_in_data_paths( - self, tmpdir, FakeConfig - ): - """Any files in data_dir undefined in cfg.data_paths will remain.""" - cfg = FakeConfig() - t_file = tmpdir.join(PRIVATE_SUBDIR, "otherfile") - os.makedirs(os.path.dirname(t_file.strpath)) - with open(t_file.strpath, "w") as f: - f.write("content") - assert [os.path.basename(t_file.strpath)] == os.listdir( - tmpdir.join(PRIVATE_SUBDIR).strpath - ) - cfg.delete_cache() - cfg.machine_token_file.delete() - assert [os.path.basename(t_file.strpath)] == os.listdir( - tmpdir.join(PRIVATE_SUBDIR).strpath - ) - - class TestProcessConfig: @pytest.mark.parametrize( "http_proxy, https_proxy, snap_is_snapd_installed, snap_http_val, " @@ -830,7 +515,7 @@ @mock.patch("uaclient.snap.configure_snap_proxy") @mock.patch("uaclient.snap.is_snapd_installed") @mock.patch("uaclient.apt.setup_apt_proxy") - @mock.patch("uaclient.config.state_files.user_config_file.write") + @mock.patch("uaclient.config.user_config_file.user_config.write") def test_process_config( self, m_write, @@ -1316,27 +1001,72 @@ assert DEFAULT_CONFIG_FILE == get_config_path() -class TestCheckLockInfo: - @pytest.mark.parametrize("lock_content", ((""), ("corrupted"))) - @mock.patch("os.path.exists", return_value=True) - @mock.patch("uaclient.system.load_file") - def test_raise_exception_for_corrupted_lock( - self, - m_load_file, - _m_path_exists, - lock_content, - FakeConfig, - ): +class TestConfigShow: + @mock.patch("uaclient.config.user_config_file.user_config.write") + def test_redact_config_data(self, _write, FakeConfig): cfg = FakeConfig() - m_load_file.return_value = lock_content - expected_msg = messages.E_INVALID_LOCK_FILE.format( - lock_file_path=cfg.data_dir + "/lock" + setattr( + cfg.user_config, + "apt_http_proxy", + "http://username:password@proxy:port", + ) + setattr( + cfg.user_config, + "apt_https_proxy", + "http://username:password@proxy:port", + ) + setattr( + cfg.user_config, + "global_apt_http_proxy", + "http://username:password@proxy:port", + ) + setattr( + cfg.user_config, + "global_apt_https_proxy", + "http://username:password@proxy:port", + ) + setattr( + cfg.user_config, + "ua_apt_http_proxy", + "http://username:password@proxy:port", + ) + setattr( + cfg.user_config, + "ua_apt_https_proxy", + "http://username:password@proxy:port", ) + setattr( + cfg.user_config, + "http_proxy", + "http://username:password@proxy:port", + ) + setattr(cfg.user_config, "https_proxy", "https://www.example.com") - with pytest.raises(exceptions.InvalidLockFile) as exc_info: - cfg.check_lock_info() + user_config_file_object = user_config_file.UserConfigFileObject() + redacted_config = user_config_file_object.redact_config_data( + cfg.user_config + ) - assert expected_msg.msg == exc_info.value.msg - assert m_load_file.call_count == 1 - assert _m_path_exists.call_count == 1 + # Assert that proxy configurations are redacted + assert getattr(redacted_config, "apt_http_proxy") == "" + assert getattr(redacted_config, "apt_https_proxy") == "" + assert ( + getattr(redacted_config, "global_apt_http_proxy") == "" + ) + assert ( + getattr(redacted_config, "global_apt_https_proxy") == "" + ) + assert getattr(redacted_config, "ua_apt_http_proxy") == "" + assert getattr(redacted_config, "ua_apt_https_proxy") == "" + assert getattr(redacted_config, "http_proxy") == "" + assert ( + getattr(redacted_config, "https_proxy") + == "https://www.example.com" + ) + + # Assert that redacting multiple times does not change the result + redacted_again = user_config_file_object.redact_config_data( + redacted_config + ) + assert redacted_config == redacted_again diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_contract.py ubuntu-advantage-tools-32~16.04/uaclient/tests/test_contract.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_contract.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/tests/test_contract.py 2024-04-23 13:37:02.000000000 +0000 @@ -249,7 +249,6 @@ assert {"test": "response"} == client.get_resource_machine_access( **kwargs ) - assert {"test": "response"} == cfg.read_cache("machine-access-cis") params = { "headers": { "user-agent": "UA-Client/{}".format(get_version()), @@ -506,14 +505,12 @@ ): cfg = FakeConfig() client = UAContractClient(cfg) - magic_attach_token_resp = ( - { - "token": "token", - "expires": "2100-06-09T18:14:55.323733Z", - "expiresIn": 600, - "userCode": "1234", - }, - ) + magic_attach_token_resp = { + "token": "token", + "expires": "2100-06-09T18:14:55.323733Z", + "expiresIn": 600, + "userCode": "1234", + } request_url.return_value = http.HTTPResponse( code=200, headers={}, @@ -717,7 +714,12 @@ True, "lxc", "8001", - IsAttachedResult(is_attached=False), + IsAttachedResult( + is_attached=False, + contract_status="none", + contract_remaining_days=0, + is_attached_and_contract_valid=False, + ), None, None, None, @@ -755,7 +757,12 @@ True, "lxc", "8001", - IsAttachedResult(is_attached=True), + IsAttachedResult( + is_attached=True, + contract_status="active", + contract_remaining_days=100, + is_attached_and_contract_valid=True, + ), mock.MagicMock( enabled_services=[ helpers.mock_with_name_attr( @@ -810,7 +817,12 @@ True, "lxc", "8001", - IsAttachedResult(is_attached=True), + IsAttachedResult( + is_attached=True, + contract_status="active", + contract_remaining_days=100, + is_attached_and_contract_valid=True, + ), mock.MagicMock( enabled_services=[ helpers.mock_with_name_attr( @@ -1234,13 +1246,9 @@ @pytest.mark.parametrize( [ "update_contract_machine_result", - "expected_write_cache_call_args", ], [ - ( - {"response": "val"}, - [mock.call("machine-id", mock.sentinel.system_machine_id)], - ), + ({"response": "val"},), ( { "response": "val", @@ -1248,12 +1256,11 @@ "machineId": mock.sentinel.response_machine_id }, }, - [mock.call("machine-id", mock.sentinel.response_machine_id)], ), ], ) @mock.patch(M_PATH + "process_entitlements_delta") - @mock.patch(M_PATH + "UAConfig.write_cache") + @mock.patch("uaclient.files.state_files.machine_id_file.write") @mock.patch(M_PATH + "system.get_machine_id") @mock.patch("uaclient.files.MachineTokenFile.write") @mock.patch(M_PATH + "UAContractClient.update_contract_machine") @@ -1271,10 +1278,9 @@ m_update_contract_machine, m_machine_token_file_write, m_get_machine_id, - m_write_cache, + m_machine_id_file_write, m_process_entitlements_deltas, update_contract_machine_result, - expected_write_cache_call_args, FakeConfig, ): m_entitlements.side_effect = [ @@ -1296,7 +1302,7 @@ assert [ mock.call(update_contract_machine_result) ] == m_machine_token_file_write.call_args_list - assert expected_write_cache_call_args == m_write_cache.call_args_list + assert 1 == m_machine_id_file_write.call_count assert [ mock.call( mock.ANY, diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_lib_auto_attach.py ubuntu-advantage-tools-32~16.04/uaclient/tests/test_lib_auto_attach.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_lib_auto_attach.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/tests/test_lib_auto_attach.py 2024-04-23 13:37:02.000000000 +0000 @@ -41,6 +41,20 @@ "cloud_config_modules": ["ubuntu-advantage", "test"], }, ), + ( + True, + { + "ubuntu_pro": {"token": "TOKEN"}, + "cloud_config_modules": ["ubuntu-advantage", "test"], + }, + ), + ( + True, + { + "ubuntu-advantage": {"token": "TOKEN"}, + "cloud_config_modules": ["ubuntu-advantage", "test"], + }, + ), ), ) @mock.patch("lib.auto_attach.get_cloudinit_init_stage") diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_lock.py ubuntu-advantage-tools-32~16.04/uaclient/tests/test_lock.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_lock.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/tests/test_lock.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,78 +1,76 @@ +import os + import mock import pytest -from uaclient.exceptions import LockHeldError +from uaclient import lock +from uaclient.defaults import DEFAULT_DATA_DIR +from uaclient.exceptions import InvalidLockFile, LockHeldError from uaclient.files.notices import Notice from uaclient.lock import RetryLock -from uaclient.messages import LOCK_HELD +from uaclient.messages import E_INVALID_LOCK_FILE, LOCK_HELD M_PATH = "uaclient.lock." M_PATH_UACONFIG = "uaclient.config.UAConfig." class TestRetryLock: + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("os.getpid", return_value=123) - @mock.patch(M_PATH_UACONFIG + "delete_cache_key") @mock.patch("uaclient.files.notices.NoticesManager.add") - @mock.patch(M_PATH_UACONFIG + "write_cache") def test_creates_and_releases_lock( self, - m_write_cache, m_add_notice, - m_delete_cache_key, _m_getpid, - FakeConfig, + _m_check_lock_info, ): - cfg = FakeConfig() - arg = mock.sentinel.arg - - def test_function(arg): - assert arg == mock.sentinel.arg + def test_function(): return mock.sentinel.success - with RetryLock(cfg=cfg, lock_holder="some operation"): - ret = test_function(arg) + with mock.patch.object(lock, "lock_data_file") as m_lock_file: + with lock.RetryLock(lock_holder="some operation"): + ret = test_function() assert mock.sentinel.success == ret assert [ - mock.call("lock", "123:some operation") - ] == m_write_cache.call_args_list + mock.call( + lock.LockData(lock_pid="123", lock_holder="some operation") + ) + ] == m_lock_file.write.call_args_list lock_msg = "Operation in progress: some operation" assert [ mock.call(Notice.OPERATION_IN_PROGRESS, lock_msg) ] == m_add_notice.call_args_list - assert [mock.call("lock")] == m_delete_cache_key.call_args_list + assert 1 == m_lock_file.delete.call_count + @mock.patch("uaclient.lock.check_lock_info", return_value=(-1, "")) @mock.patch("os.getpid", return_value=123) - @mock.patch(M_PATH_UACONFIG + "delete_cache_key") @mock.patch("uaclient.files.notices.NoticesManager.add") - @mock.patch(M_PATH_UACONFIG + "write_cache") def test_creates_and_releases_lock_when_error_occurs( self, - m_write_cache, m_add_notice, - m_delete_cache_key, _m_getpid, - FakeConfig, + _m_check_lock_info, ): - cfg = FakeConfig() - def test_function(): raise RuntimeError("test") with pytest.raises(RuntimeError) as exc: - with RetryLock(cfg=cfg, lock_holder="some operation"): - test_function() + with mock.patch.object(lock, "lock_data_file") as m_lock_file: + with RetryLock(lock_holder="some operation"): + test_function() assert "test" == str(exc.value) assert [ - mock.call("lock", "123:some operation") - ] == m_write_cache.call_args_list + mock.call( + lock.LockData(lock_pid="123", lock_holder="some operation") + ) + ] == m_lock_file.write.call_args_list lock_msg = "Operation in progress: some operation" assert [ mock.call(Notice.OPERATION_IN_PROGRESS, lock_msg) ] == m_add_notice.call_args_list - assert [mock.call("lock")] == m_delete_cache_key.call_args_list + assert 1 == m_lock_file.delete.call_count @mock.patch(M_PATH + "time.sleep") @mock.patch( @@ -87,13 +85,9 @@ None, ], ) - def test_spins_when_lock_held( - self, m_single_attempt_lock_enter, m_sleep, FakeConfig - ): - cfg = FakeConfig() - - with RetryLock( - cfg=cfg, lock_holder="request", sleep_time=1, max_retries=3 + def test_spins_when_lock_held(self, m_single_attempt_lock_enter, m_sleep): + with lock.RetryLock( + lock_holder="request", sleep_time=1, max_retries=3 ): pass @@ -118,14 +112,10 @@ ], ) def test_raises_lock_held_after_max_retries( - self, m_single_attempt_lock_enter, m_sleep, FakeConfig + self, m_single_attempt_lock_enter, m_sleep ): - cfg = FakeConfig() - with pytest.raises(LockHeldError) as exc: - with RetryLock( - cfg=cfg, lock_holder="request", sleep_time=1, max_retries=2 - ): + with RetryLock(lock_holder="request", sleep_time=1, max_retries=2): pass assert ( @@ -139,3 +129,24 @@ mock.call(), ] == m_single_attempt_lock_enter.call_args_list assert [mock.call(1)] == m_sleep.call_args_list + + +class TestCheckLockInfo: + @pytest.mark.parametrize("lock_content", ((""), ("corrupted"))) + @mock.patch("uaclient.system.load_file") + def test_raise_exception_for_corrupted_lock( + self, + m_load_file, + lock_content, + ): + m_load_file.return_value = lock_content + + expected_msg = E_INVALID_LOCK_FILE.format( + lock_file_path=os.path.join(DEFAULT_DATA_DIR, "lock") + ) + + with pytest.raises(InvalidLockFile) as exc_info: + lock.check_lock_info() + + assert expected_msg.msg == exc_info.value.msg + assert m_load_file.call_count == 1 diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_log.py ubuntu-advantage-tools-32~16.04/uaclient/tests/test_log.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_log.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/tests/test_log.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,11 +1,12 @@ import json import logging +import sys from io import StringIO +import mock import pytest -from uaclient import log as pro_log -from uaclient import util +from uaclient import log, util LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) LOG_FMT = "%(asctime)s%(name)s%(funcName)s%(lineno)s\ @@ -18,8 +19,8 @@ def test_unredacted_text(self, caplog_text): text = "Bearer SEKRET" LOG.info(text) - log = caplog_text() - assert text in log + log_text = caplog_text() + assert text in log_text @pytest.mark.parametrize( "raw_log,expected", @@ -117,10 +118,10 @@ ) @pytest.mark.parametrize("caplog_text", [logging.INFO], indirect=True) def test_redacted_text(self, caplog_text, raw_log, expected): - LOG.addFilter(pro_log.RedactionFilter()) + LOG.addFilter(log.RegexRedactionFilter()) LOG.info(raw_log) - log = caplog_text() - assert expected in log + log_text = caplog_text() + assert expected in log_text class TestLoggerFormatter: @@ -142,7 +143,7 @@ def test_valid_json_output( self, caplog_text, message, level, log_fn, levelname, extra ): - formatter = pro_log.JsonArrayFormatter(LOG_FMT, DATE_FMT) + formatter = log.JsonArrayFormatter(LOG_FMT, DATE_FMT) buffer = StringIO() sh = logging.StreamHandler(buffer) sh.setLevel(level) @@ -158,3 +159,82 @@ assert val[6].get("key") == extra.get("key") else: assert 7 == len(val) + + +class TestLogHelpers: + @pytest.mark.parametrize( + [ + "we_are_currently_root", + "expected", + ], + [ + (True, "cfg_log_file"), + (False, "user_log_file"), + ], + ) + @mock.patch( + "uaclient.log.get_user_log_file", + return_value="user_log_file", + ) + @mock.patch( + "uaclient.config.UAConfig.log_file", + new_callable=mock.PropertyMock, + return_value="cfg_log_file", + ) + @mock.patch("uaclient.util.we_are_currently_root") + def test_get_user_or_root_log_file_path( + self, + m_we_are_currently_root, + m_cfg_log_file, + m_get_user_log_file, + we_are_currently_root, + expected, + ): + """ + Tests that the correct log_file path is retrieved + when the user is root and non-root + """ + m_we_are_currently_root.return_value = we_are_currently_root + result = log.get_user_or_root_log_file_path() + # ensure mocks are used properly + assert m_cfg_log_file.call_count + m_get_user_log_file.call_count == 1 + if we_are_currently_root: + assert m_cfg_log_file.call_count == 1 + else: + assert m_get_user_log_file.call_count == 1 + # ensure correct log_file path is returned + assert expected == result + + +@mock.patch("uaclient.log.logging.FileHandler") +@mock.patch("uaclient.log.pathlib.Path") +@mock.patch("uaclient.log.logging.getLogger") +class TestSetupCliLogging: + def test_correct_handlers_added_to_logger( + self, + m_getLogger, + m_path, + m_FileHandler, + ): + fake_logger = mock.MagicMock( + handlers=[logging.StreamHandler(sys.stderr)] + ) + m_getLogger.return_value = fake_logger + fake_file_handler = mock.MagicMock() + m_FileHandler.return_value = fake_file_handler + + log.setup_cli_logging(11, "fakefile") + assert len(fake_logger.handlers) == 0 # handlers is cleared + assert [mock.call(11)] == fake_file_handler.setLevel.call_args_list + assert [ + mock.call(fake_file_handler) + ] == fake_logger.addHandler.call_args_list + + def test_log_file_created_if_not_present( + self, m_getLogger, m_path, m_FileHandler + ): + m_path.return_value.exists.return_value = False + log.setup_cli_logging(logging.INFO, "fakefile") + assert m_path.return_value.touch.call_args_list == [ + mock.call(mode=0o640) + ] diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_reboot_cmds.py ubuntu-advantage-tools-32~16.04/uaclient/tests/test_reboot_cmds.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_reboot_cmds.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/tests/test_reboot_cmds.py 2024-04-23 13:37:02.000000000 +0000 @@ -28,30 +28,32 @@ "enabled", None, None, - [mock.call()], - [mock.call(cleanup_on_failure=False)], + [mock.call(progress=mock.ANY)], + [mock.call(progress=mock.ANY, cleanup_on_failure=False)], does_not_raise(), ), ( "enabled", Exception(), None, - [mock.call()], - [mock.call(cleanup_on_failure=False)], + [mock.call(progress=mock.ANY)], + [mock.call(progress=mock.ANY, cleanup_on_failure=False)], does_not_raise(), ), ( "enabled", Exception(), fakes.FakeUbuntuProError(), - [mock.call()], - [mock.call(cleanup_on_failure=False)], + [mock.call(progress=mock.ANY)], + [mock.call(progress=mock.ANY, cleanup_on_failure=False)], pytest.raises(exceptions.UbuntuProError), ), ], ) + @mock.patch("uaclient.files.state_files.status_cache_file.read") def test_fix_pro_pkg_holds( self, + m_status_cache_file_read, m_fips_status, m_fips_setup_apt_config, m_fips_install_packages, @@ -66,10 +68,9 @@ m_fips_setup_apt_config.side_effect = fips_setup_apt_config_side_effect m_fips_install_packages.side_effect = fips_install_packages_side_effect cfg = FakeConfig() - fake_status_cache = { + m_status_cache_file_read.return_value = { "services": [{"name": "fips", "status": fips_status}] } - cfg.write_cache("status-cache", fake_status_cache) with expected_raises: fix_pro_pkg_holds(cfg) @@ -148,7 +149,7 @@ if expected_calls: assert [ - mock.call(cfg=mock.ANY, lock_holder="pro-reboot-cmds") + mock.call(lock_holder="pro-reboot-cmds") ] == m_spin_lock.call_args_list assert [mock.call(mock.ANY)] == m_fix_pro_pkg_holds.call_args_list assert [mock.call(mock.ANY)] == m_refresh_contract.call_args_list diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_status.py ubuntu-advantage-tools-32~16.04/uaclient/tests/test_status.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_status.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/tests/test_status.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,7 +1,5 @@ import copy import datetime -import os -import stat import string import mock @@ -13,7 +11,7 @@ entitlement_factory, valid_services, ) -from uaclient.entitlements.base import IncompatibleService +from uaclient.entitlements.base import EntitlementWithMessage from uaclient.entitlements.entitlement_status import ( ApplicationStatus, ContractStatus, @@ -24,6 +22,7 @@ from uaclient.entitlements.ros import ROSEntitlement from uaclient.entitlements.tests.test_base import ConcreteTestEntitlement from uaclient.files.notices import Notice, NoticesManager +from uaclient.files.user_config_file import UserConfigData from uaclient.status import ( DEFAULT_STATUS, TxtColor, @@ -314,10 +313,12 @@ return False @pytest.mark.parametrize("show_all", (True, False)) + @mock.patch("uaclient.files.state_files.status_cache_file.write") @mock.patch("uaclient.status.get_available_resources") def test_root_unattached( self, m_get_available_resources, + _m_status_cache_file, _m_should_reboot, _m_remove_notice, m_on_supported_kernel, @@ -397,6 +398,7 @@ ), ), ) + @mock.patch("uaclient.files.state_files.status_cache_file.write") @mock.patch( M_PATH + "livepatch.LivepatchEntitlement.application_status", return_value=(ApplicationStatus.DISABLED, ""), @@ -406,6 +408,7 @@ self, m_get_avail_resources, _m_livepatch_status, + _m_status_cache_file, _m_should_reboot, _m_remove_notice, m_on_supported_kernel, @@ -497,13 +500,15 @@ expected_services = [ { "description": cls.description, - "entitled": uf_entitled - if cls.name in resource_names - else default_entitled, + "entitled": ( + uf_entitled + if cls.name in resource_names + else default_entitled + ), "name": cls.name, - "status": uf_status - if cls.name in resource_names - else default_status, + "status": ( + uf_status if cls.name in resource_names else default_status + ), "status_details": mock.ANY, "description_override": None, "available": mock.ANY, @@ -564,17 +569,25 @@ m_get_cfg_status.return_value = DEFAULT_CFG_STATUS assert expected == status.status(cfg=cfg, show_all=True) + @mock.patch("uaclient.files.state_files.status_cache_file.write") @mock.patch("uaclient.util.we_are_currently_root") @mock.patch("uaclient.status.get_available_resources") + @mock.patch( + "uaclient.files.user_config_file.UserConfigFileObject.public_config", + new_callable=mock.PropertyMock, + ) def test_nonroot_unattached_is_same_as_unattached_root( self, + m_public_config, m_get_available_resources, m_we_are_currently_root, + _m_status_cache_file, _m_should_reboot, _m_remove_notice, m_on_supported_kernel, FakeConfig, ): + m_public_config.return_value = UserConfigData() m_get_available_resources.return_value = [ {"name": "esm-infra", "available": True} ] @@ -588,17 +601,25 @@ assert root_unattached_status == nonroot_status + @mock.patch("uaclient.files.state_files.status_cache_file.write") @mock.patch("uaclient.util.we_are_currently_root") @mock.patch("uaclient.status.get_available_resources") + @mock.patch( + "uaclient.files.user_config_file.UserConfigFileObject.public_config", + new_callable=mock.PropertyMock, + ) def test_root_and_non_root_are_same_attached( self, + m_public_config, m_get_available_resources, m_we_are_currently_root, + _m_status_cache_file, _m_should_reboot, _m_remove_notice, m_on_supported_kernel, FakeConfig, ): + m_public_config.return_value = UserConfigData() m_we_are_currently_root.return_value = True root_cfg = FakeConfig.for_attached_machine() root_status = status.status(cfg=root_cfg) @@ -607,22 +628,6 @@ normal_status = status.status(cfg=normal_cfg) assert normal_status == root_status - @mock.patch("uaclient.status.get_available_resources", return_value=[]) - def test_cache_file_is_written_world_readable( - self, - _m_get_available_resources, - _m_should_reboot, - m_remove_notice, - m_on_supported_kernel, - FakeConfig, - ): - cfg = FakeConfig() - status.status(cfg=cfg) - - assert 0o644 == stat.S_IMODE( - os.lstat(cfg.data_path("status-cache")).st_mode - ) - @pytest.mark.parametrize("variants_in_contract", ((True), (False))) @pytest.mark.parametrize("show_all", (True, False)) @pytest.mark.parametrize( @@ -642,6 +647,7 @@ ), ) @pytest.mark.usefixtures("all_resources_available") + @mock.patch("uaclient.files.state_files.status_cache_file.write") @mock.patch( M_PATH + "fips.FIPSCommonEntitlement.application_status", return_value=(ApplicationStatus.DISABLED, ""), @@ -672,6 +678,7 @@ m_livepatch_uf_status, _m_livepatch_status, _m_fips_status, + _m_status_cache_file, _m_should_reboot, _m_remove_notice, m_on_supported_kernel, @@ -871,18 +878,26 @@ assert expected_calls == mock_notice.remove.call_args_list @pytest.mark.usefixtures("all_resources_available") + @mock.patch("uaclient.files.state_files.status_cache_file.write") @mock.patch("uaclient.util.we_are_currently_root") @mock.patch("uaclient.status.get_available_resources") + @mock.patch( + "uaclient.files.user_config_file.UserConfigFileObject.public_config", + new_callable=mock.PropertyMock, + ) def test_expires_handled_appropriately( self, + m_public_config, _m_get_available_resources, m_we_are_currently_root, + _m_status_cache_file, _m_should_reboot, _m_remove_notice, m_on_supported_kernel, all_resources_available, FakeConfig, ): + m_public_config.return_value = UserConfigData() m_we_are_currently_root.return_value = True token = { "availableResources": all_resources_available, @@ -927,20 +942,30 @@ "uaclient.files.state_files.reboot_cmd_marker_file", new_callable=mock.PropertyMock, ) + @mock.patch( + "uaclient.files.user_config_file.UserConfigFileObject.public_config", + new_callable=mock.PropertyMock, + ) + @mock.patch("uaclient.files.state_files.status_cache_file.read") + @mock.patch("uaclient.files.state_files.status_cache_file.write") @mock.patch("uaclient.status.get_available_resources", return_value={}) def test_nonroot_user_does_not_use_cache( self, _m_get_available_resources, + _m_status_cache_file_write, + m_status_cache_file_read, + m_public_config, m_reboot_cmd_marker_file, _m_should_reboot, m_remove_notice, m_on_supported_kernel, FakeConfig, ): + m_public_config.return_value = UserConfigData() m_reboot_cmd_marker_file.is_present = True cached_status = {"pass": True} + m_status_cache_file_read.return_value = cached_status cfg = FakeConfig() - cfg.write_cache("status-cache", cached_status) before = status.status(cfg=cfg) # Even non-root users can update execution_status details @@ -1024,7 +1049,7 @@ ([], []), ( [ - IncompatibleService( + EntitlementWithMessage( FIPSEntitlement, messages.NamedMessage("code", "msg") ) ], @@ -1032,10 +1057,10 @@ ), ( [ - IncompatibleService( + EntitlementWithMessage( FIPSEntitlement, messages.NamedMessage("code", "msg") ), - IncompatibleService( + EntitlementWithMessage( ROSEntitlement, messages.NamedMessage("code2", "msg2") ), ], diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_system.py ubuntu-advantage-tools-32~16.04/uaclient/tests/test_system.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_system.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/tests/test_system.py 2024-04-23 13:37:02.000000000 +0000 @@ -968,15 +968,26 @@ assert expected == system.get_release_info.__wrapped__() +@mock.patch("uaclient.files.state_files.machine_id_file.write") class TestGetMachineId: - def test_get_machine_id_from_config(self, FakeConfig): + def test_get_machine_id_from_config( + self, _m_machine_id_file_write, FakeConfig + ): cfg = FakeConfig.for_attached_machine() value = system.get_machine_id(cfg) assert "test_machine_id" == value - def test_get_machine_id_from_etc_machine_id(self, FakeConfig, tmpdir): + @mock.patch("uaclient.files.state_files.machine_id_file.read") + def test_get_machine_id_from_etc_machine_id( + self, + m_machine_id_file_read, + _m_machine_id_file_write, + FakeConfig, + tmpdir, + ): """Presence of /etc/machine-id is returned if it exists.""" etc_machine_id = tmpdir.join("etc-machine-id") + m_machine_id_file_read.return_value = None assert "/etc/machine-id" == system.ETC_MACHINE_ID etc_machine_id.write("etc-machine-id") cfg = FakeConfig() @@ -990,10 +1001,16 @@ assert value == cached_value assert "etc-machine-id" == value + @mock.patch("uaclient.files.state_files.machine_id_file.read") def test_get_machine_id_from_var_lib_dbus_machine_id( - self, FakeConfig, tmpdir + self, + m_machine_id_file_read, + _m_machine_id_file_write, + FakeConfig, + tmpdir, ): """fallback to /var/lib/dbus/machine-id""" + m_machine_id_file_read.return_value = None etc_machine_id = tmpdir.join("etc-machine-id") dbus_machine_id = tmpdir.join("dbus-machine-id") assert "/var/lib/dbus/machine-id" == system.DBUS_MACHINE_ID @@ -1008,30 +1025,26 @@ value = system.get_machine_id(cfg) assert "dbus-machine-id" == value + @mock.patch("uaclient.files.state_files.machine_id_file.read") def test_get_machine_id_uses_machine_id_from_data_dir( - self, FakeConfig, tmpdir + self, m_machine_id_file_read, _m_machine_id_file_write, FakeConfig ): - """When no machine-id is found, use machine-id from data_dir.""" - data_machine_id = tmpdir.mkdir("private").join("machine-id") - data_machine_id.write("data-machine-id") - cfg = FakeConfig() - - def fake_exists(path): - return bool(path == data_machine_id.strpath) + m_machine_id_file_read.return_value = "data-machine-id" with mock.patch("uaclient.util.os.path.exists") as m_exists: - m_exists.side_effect = fake_exists + m_exists.return_value = False value = system.get_machine_id(cfg) assert "data-machine-id" == value + @mock.patch("uaclient.files.state_files.machine_id_file.read") def test_get_machine_id_create_machine_id_in_data_dir( - self, FakeConfig, tmpdir + self, m_machine_id_file_read, m_machine_id_file_write, FakeConfig ): """When no machine-id is found, create one in data_dir using uuid4.""" - data_machine_id = tmpdir.mkdir("private").join("machine-id") cfg = FakeConfig() + m_machine_id_file_read.return_value = None with mock.patch("uaclient.util.os.path.exists") as m_exists: with mock.patch("uaclient.system.uuid.uuid4") as m_uuid4: m_exists.return_value = False @@ -1039,14 +1052,22 @@ "0123456789abcdef0123456789abcdef" ) value = system.get_machine_id(cfg) + assert "01234567-89ab-cdef-0123-456789abcdef" == value - assert "01234567-89ab-cdef-0123-456789abcdef" == data_machine_id.read() + assert [ + mock.call("01234567-89ab-cdef-0123-456789abcdef") + ] == m_machine_id_file_write.call_args_list @pytest.mark.parametrize("empty_value", ["", "\n"]) + @mock.patch("uaclient.files.state_files.machine_id_file.read") def test_fallback_used_if_all_other_files_are_empty( - self, FakeConfig, tmpdir, empty_value + self, + m_machine_id_file_read, + m_machine_id_file_write, + FakeConfig, + empty_value, ): - data_machine_id = tmpdir.mkdir("private").join("machine-id") + m_machine_id_file_read.return_value = None cfg = FakeConfig().for_attached_machine( machine_token={"some": "thing"}, ) @@ -1064,7 +1085,9 @@ ) value = system.get_machine_id(cfg) assert "01234567-89ab-cdef-0123-456789abcdef" == value - assert "01234567-89ab-cdef-0123-456789abcdef" == data_machine_id.read() + assert [ + mock.call("01234567-89ab-cdef-0123-456789abcdef") + ] == m_machine_id_file_write.call_args_list class TestShouldReboot: @@ -1443,7 +1466,7 @@ "expected", ], ( - (True, None, None, "/run/ubuntu-advantage/"), + (True, None, None, "/run/ubuntu-advantage"), (False, None, "/home/user", "/home/user/.cache/ubuntu-pro"), (False, "/something", "/home/user", "/something/ubuntu-pro"), ), diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_ua_timer.py ubuntu-advantage-tools-32~16.04/uaclient/tests/test_ua_timer.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_ua_timer.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/tests/test_ua_timer.py 2024-04-23 13:37:02.000000000 +0000 @@ -210,10 +210,8 @@ ): m_run_interval_seconds.return_value = config_value m_cfg = mock.MagicMock() - type( - m_cfg.machine_token_file - ).activity_ping_interval = mock.PropertyMock( - return_value=activity_ping_interval_value + type(m_cfg.machine_token_file).activity_ping_interval = ( + mock.PropertyMock(return_value=activity_ping_interval_value) ) metering_job = MeteringTimedJob( diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_util.py ubuntu-advantage-tools-32~16.04/uaclient/tests/test_util.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/tests/test_util.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/tests/test_util.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,11 +1,28 @@ """Tests related to uaclient.util module.""" + import datetime import json import mock import pytest -from uaclient import exceptions, messages, util +from uaclient import exceptions, messages, secret_manager, util + + +@pytest.fixture +def secret_manager_fixture(): + manager = secret_manager.SecretManager() + secrets = [ + "SEKRET", + "S3-Kr3T", + "S3kR3T", + "SEket.124-_ys", + "reg key", + "reg-key", + ] + for secret in secrets: + manager.add_secret(secret) + return manager class TestGetDictDeltas: @@ -295,6 +312,126 @@ """Redact all sensitive matches from log messages.""" assert expected == util.redact_sensitive_logs(raw_log) + @pytest.mark.parametrize( + "raw_log,expected", + ( + ("Super valuable", "Super valuable"), + ( + "Executed with sys.argv: ['/usr/bin/ua', 'attach', 'SEKRET']", + "Executed with sys.argv:" + " ['/usr/bin/ua', 'attach', '']", + ), + ( + "'resourceTokens': [{'token': 'SEKRET', 'type': 'cc-eal'}]'", + "'resourceTokens':" + " [{'token': '', 'type': 'cc-eal'}]'", + ), + ( + "'machineToken': 'SEKRET', 'machineTokenInfo': 'blah'", + "'machineToken': '', 'machineTokenInfo': 'blah'", + ), + ( + "Failed running command '/usr/lib/apt/apt-helper download-file" + "https://bearer:S3-Kr3T@esm.ubuntu.com/infra/ubuntu/pool/ " + "[exit(100)]. Message: Download of file failed" + " pkgAcquire::Run (13: Permission denied)", + "Failed running command '/usr/lib/apt/apt-helper download-file" + "https://bearer:@esm.ubuntu.com/infra/ubuntu/pool/ " + "[exit(100)]. Message: Download of file failed" + " pkgAcquire::Run (13: Permission denied)", + ), + ( + "/snap/bin/canonical-livepatch enable S3-Kr3T, foobar", + "/snap/bin/canonical-livepatch enable , foobar", + ), + ( + "Contract value for 'resourceToken' changed to S3kR3T", + "Contract value for 'resourceToken' changed to ", + ), + ( + "data: {'contractToken': 'SEKRET', " + "'contractTokenInfo':{'expiry'}}", + "data: {'contractToken': '', " + "'contractTokenInfo':{'expiry'}}", + ), + ( + "data: {'resourceToken': 'SEKRET', " + "'entitlement': {'affordances':'blah blah' }}", + "data: {'resourceToken': '', " + "'entitlement': {'affordances':'blah blah' }}", + ), + ( + "https://contracts.canonical.com/v1/resources/livepatch" + "?token=SEKRET: invalid token", + "https://contracts.canonical.com/v1/resources/livepatch" + "?token=: invalid token", + ), + ( + 'data: {"identityToken": "SEket.124-_ys"}', + 'data: {"identityToken": ""}', + ), + ( + "http://metadata/computeMetadata/v1/instance/service-accounts/" + "default/identity?audience=contracts.canon, data: none", + "http://metadata/computeMetadata/v1/instance/service-accounts/" + "default/identity?audience=contracts.canon, data: none", + ), + ( + "'token': 'SEKRET'", + "'token': ''", + ), + ( + "'userCode': 'SEKRET'", + "'userCode': ''", + ), + ( + "'magic_token=SEKRET'", + "'magic_token='", + ), + ( + "--account-name name --registration-key=reg-key --silent", + "--account-name name --registration-key= --silent", + ), + ( + '--account-name name --registration-key="reg key" --silent', + '--account-name name --registration-key="" --silent', + ), + ( + "--account-name name --registration-key='reg key' --silent", + "--account-name name --registration-key='' --silent", + ), + ( + "--account-name name --registration-key reg-key --silent", + "--account-name name --registration-key --silent", + ), + ( + '--account-name name --registration-key "reg key" --silent', + '--account-name name --registration-key "" --silent', + ), + ( + "--account-name name --registration-key 'reg key' --silent", + "--account-name name --registration-key '' --silent", + ), + ( + "--account-name name -p reg-key --silent", + "--account-name name -p --silent", + ), + ( + '--account-name name -p "reg key" --silent', + '--account-name name -p "" --silent', + ), + ( + "--account-name name -p 'reg key' --silent", + "--account-name name -p '' --silent", + ), + ), + ) + def test_secret_redaction_filter( + self, secret_manager_fixture, raw_log, expected + ): + """Redact all sensitive matches from log messages.""" + assert expected == secret_manager_fixture.redact_secrets(raw_log) + class TestParseRFC3339Date: @pytest.mark.parametrize( diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/timer/metering.py ubuntu-advantage-tools-32~16.04/uaclient/timer/metering.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/timer/metering.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/timer/metering.py 2024-04-23 13:37:02.000000000 +0000 @@ -4,11 +4,11 @@ from uaclient import config from uaclient.api.u.pro.status.is_attached.v1 import _is_attached -from uaclient.cli import assert_lock_file +from uaclient.cli import cli_util from uaclient.contract import UAContractClient -@assert_lock_file("timer metering job") +@cli_util.assert_lock_file("timer metering job") def metering_enabled_resources(cfg: config.UAConfig) -> bool: # We only run this job if there is no other job running. # The reason for that is to avoid potential conflicts with diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/timer/tests/test_update_messaging.py ubuntu-advantage-tools-32~16.04/uaclient/timer/tests/test_update_messaging.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/timer/tests/test_update_messaging.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/timer/tests/test_update_messaging.py 2024-04-23 13:37:02.000000000 +0000 @@ -8,44 +8,47 @@ PackageUpdatesResult, UpdateSummary, ) -from uaclient.contract import ContractExpiryStatus, get_contract_expiry_status +from uaclient.api.u.pro.status.is_attached.v1 import ContractExpiryStatus from uaclient.entitlements.entitlement_status import ApplicationStatus -from uaclient.timer.update_messaging import update_motd_messages +from uaclient.files import notices +from uaclient.timer.update_messaging import ( + update_contract_expiry, + update_motd_messages, +) M_PATH = "uaclient.timer.update_messaging." class TestGetContractExpiryStatus: @pytest.mark.parametrize( - "contract_remaining_days,expected_status", + "expiry,is_updated", ( - (21, ContractExpiryStatus.ACTIVE), - (20, ContractExpiryStatus.ACTIVE_EXPIRED_SOON), - (-1, ContractExpiryStatus.EXPIRED_GRACE_PERIOD), - (-20, ContractExpiryStatus.EXPIRED), + ( + datetime.datetime( + 2040, + 5, + 8, + 19, + 2, + 26, + tzinfo=datetime.timezone.utc, + ), + False, + ), + ( + datetime.datetime( + 2042, + 5, + 8, + 19, + 2, + 26, + tzinfo=datetime.timezone.utc, + ), + True, + ), ), ) - def test_contract_expiry_status_based_on_remaining_days( - self, contract_remaining_days, expected_status, FakeConfig - ): - """Return a tuple of ContractExpiryStatus and remaining_days""" - now = datetime.datetime.utcnow() - expire_date = now + datetime.timedelta(days=contract_remaining_days) - cfg = FakeConfig.for_attached_machine() - m_token = cfg.machine_token - m_token["machineTokenInfo"]["contractInfo"][ - "effectiveTo" - ] = expire_date - - assert ( - expected_status, - contract_remaining_days, - ) == get_contract_expiry_status(cfg) - - @pytest.mark.parametrize( - "expiry,is_updated", - (("2040-05-08T19:02:26Z", False), ("2042-05-08T19:02:26Z", True)), - ) @mock.patch("uaclient.files.MachineTokenFile.write") @mock.patch(M_PATH + "contract.UAContractClient.get_contract_machine") def test_update_contract_expiry( @@ -54,21 +57,23 @@ m_machine_token_write, expiry, is_updated, + FakeConfig, ): m_get_contract_machine.return_value = { "machineTokenInfo": {"contractInfo": {"effectiveTo": expiry}} } + cfg = FakeConfig.for_attached_machine() + update_contract_expiry(cfg) if is_updated: - 1 == m_machine_token_write.call_count + assert 1 == m_machine_token_write.call_count else: - 0 == m_machine_token_write.call_count + assert 0 == m_machine_token_write.call_count class TestUpdateMotdMessages: @pytest.mark.parametrize( [ - "attached", - "contract_expiry_statuses", + "is_attached_side_effect", "is_current_series_active_esm", "infra_status", "is_current_series_lts", @@ -76,14 +81,22 @@ "updates", "expected", "update_contract_expiry_calls", + "notices_remove_calls", "ensure_file_absent_calls", "write_file_calls", + "notices_add_calls", ], [ ( # not attached - False, - [], + [ + mock.MagicMock( + is_attached=False, + contract_status=None, + contract_remaining_days=0, + is_attached_and_contract_valid=False, + ) + ], False, None, False, @@ -93,11 +106,19 @@ [], [], [], + [], + [], ), ( # somehow attached but none contract status - True, - [(ContractExpiryStatus.NONE, None)], + [ + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.NONE.value, + contract_remaining_days=0, + is_attached_and_contract_valid=False, + ) + ], False, None, False, @@ -105,13 +126,21 @@ None, True, [], + [mock.call(notices.Notice.CONTRACT_EXPIRED)], [mock.call(mock.ANY)], [], + [], ), ( # active contract - True, - [(ContractExpiryStatus.ACTIVE, None)], + [ + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.ACTIVE.value, + contract_remaining_days=0, + is_attached_and_contract_valid=True, + ) + ], False, None, False, @@ -119,15 +148,26 @@ None, True, [], + [mock.call(notices.Notice.CONTRACT_EXPIRED)], [mock.call(mock.ANY)], [], + [], ), ( # expiring soon contract, updated to be active - True, [ - (ContractExpiryStatus.ACTIVE_EXPIRED_SOON, None), - (ContractExpiryStatus.ACTIVE, None), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.ACTIVE_EXPIRED_SOON.value, # noqa + contract_remaining_days=0, + is_attached_and_contract_valid=True, + ), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.ACTIVE.value, + contract_remaining_days=0, + is_attached_and_contract_valid=True, + ), ], False, None, @@ -136,15 +176,26 @@ None, True, [mock.call(mock.ANY)], + [mock.call(notices.Notice.CONTRACT_EXPIRED)], [mock.call(mock.ANY)], [], + [], ), ( # expired grace period contract, updated to be active - True, [ - (ContractExpiryStatus.EXPIRED_GRACE_PERIOD, None), - (ContractExpiryStatus.ACTIVE, None), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.EXPIRED_GRACE_PERIOD.value, # noqa + contract_remaining_days=0, + is_attached_and_contract_valid=True, + ), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.ACTIVE.value, + contract_remaining_days=0, + is_attached_and_contract_valid=True, + ), ], False, None, @@ -153,15 +204,26 @@ None, True, [mock.call(mock.ANY)], + [mock.call(notices.Notice.CONTRACT_EXPIRED)], [mock.call(mock.ANY)], [], + [], ), ( # expired contract, updated to be active - True, [ - (ContractExpiryStatus.EXPIRED, None), - (ContractExpiryStatus.ACTIVE, None), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.EXPIRED.value, + contract_remaining_days=0, + is_attached_and_contract_valid=True, + ), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.ACTIVE.value, + contract_remaining_days=0, + is_attached_and_contract_valid=True, + ), ], False, None, @@ -170,15 +232,26 @@ None, True, [mock.call(mock.ANY)], + [mock.call(notices.Notice.CONTRACT_EXPIRED)], [mock.call(mock.ANY)], [], + [], ), ( # expiring soon for real - True, [ - (ContractExpiryStatus.ACTIVE_EXPIRED_SOON, 3), - (ContractExpiryStatus.ACTIVE_EXPIRED_SOON, 3), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.ACTIVE_EXPIRED_SOON.value, # noqa + contract_remaining_days=3, + is_attached_and_contract_valid=True, + ), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.ACTIVE_EXPIRED_SOON.value, # noqa + contract_remaining_days=3, + is_attached_and_contract_valid=True, + ), ], False, None, @@ -187,6 +260,7 @@ None, True, [mock.call(mock.ANY)], + [mock.call(notices.Notice.CONTRACT_EXPIRED)], [], [ mock.call( @@ -197,13 +271,23 @@ + "\n\n", ) ], + [], ), ( # expired grace period for real - True, [ - (ContractExpiryStatus.EXPIRED_GRACE_PERIOD, -3), - (ContractExpiryStatus.EXPIRED_GRACE_PERIOD, -3), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.EXPIRED_GRACE_PERIOD.value, # noqa + contract_remaining_days=-3, + is_attached_and_contract_valid=True, + ), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.EXPIRED_GRACE_PERIOD.value, # noqa + contract_remaining_days=-3, + is_attached_and_contract_valid=True, + ), ], False, None, @@ -212,6 +296,7 @@ None, True, [mock.call(mock.ANY)], + [mock.call(notices.Notice.CONTRACT_EXPIRED)], [], [ mock.call( @@ -222,13 +307,23 @@ + "\n\n", ) ], + [], ), ( # expired, eol release, esm-infra not enabled - True, [ - (ContractExpiryStatus.EXPIRED, 3), - (ContractExpiryStatus.EXPIRED, 3), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.EXPIRED.value, + contract_remaining_days=-30, + is_attached_and_contract_valid=True, + ), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.EXPIRED.value, + contract_remaining_days=-30, + is_attached_and_contract_valid=True, + ), ], True, (ApplicationStatus.DISABLED, None), @@ -238,14 +333,25 @@ True, [mock.call(mock.ANY)], [], + [], [mock.call(mock.ANY, messages.CONTRACT_EXPIRED + "\n\n")], + [mock.call(notices.Notice.CONTRACT_EXPIRED)], ), ( # expired, lts release, esm-apps not enabled - True, [ - (ContractExpiryStatus.EXPIRED, 3), - (ContractExpiryStatus.EXPIRED, 3), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.EXPIRED.value, + contract_remaining_days=-30, + is_attached_and_contract_valid=True, + ), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.EXPIRED.value, + contract_remaining_days=-30, + is_attached_and_contract_valid=True, + ), ], False, None, @@ -255,14 +361,25 @@ True, [mock.call(mock.ANY)], [], + [], [mock.call(mock.ANY, messages.CONTRACT_EXPIRED + "\n\n")], + [mock.call(notices.Notice.CONTRACT_EXPIRED)], ), ( # expired, interim release - True, [ - (ContractExpiryStatus.EXPIRED, 3), - (ContractExpiryStatus.EXPIRED, 3), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.EXPIRED.value, + contract_remaining_days=-30, + is_attached_and_contract_valid=True, + ), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.EXPIRED.value, + contract_remaining_days=-30, + is_attached_and_contract_valid=True, + ), ], False, None, @@ -272,14 +389,25 @@ True, [mock.call(mock.ANY)], [], + [], [mock.call(mock.ANY, messages.CONTRACT_EXPIRED + "\n\n")], + [mock.call(notices.Notice.CONTRACT_EXPIRED)], ), ( # expired, eol release, esm-infra enabled - True, [ - (ContractExpiryStatus.EXPIRED, 3), - (ContractExpiryStatus.EXPIRED, 3), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.EXPIRED.value, + contract_remaining_days=-30, + is_attached_and_contract_valid=True, + ), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.EXPIRED.value, + contract_remaining_days=-30, + is_attached_and_contract_valid=True, + ), ], True, (ApplicationStatus.ENABLED, None), @@ -289,6 +417,7 @@ True, [mock.call(mock.ANY)], [], + [], [ mock.call( mock.ANY, @@ -298,13 +427,23 @@ + "\n\n", ) ], + [mock.call(notices.Notice.CONTRACT_EXPIRED)], ), ( # expired, lts release, esm-apps enabled - True, [ - (ContractExpiryStatus.EXPIRED, 3), - (ContractExpiryStatus.EXPIRED, 3), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.EXPIRED.value, + contract_remaining_days=-30, + is_attached_and_contract_valid=True, + ), + mock.MagicMock( + is_attached=True, + contract_status=ContractExpiryStatus.EXPIRED.value, + contract_remaining_days=-30, + is_attached_and_contract_valid=True, + ), ], False, None, @@ -314,6 +453,7 @@ True, [mock.call(mock.ANY)], [], + [], [ mock.call( mock.ANY, @@ -323,6 +463,7 @@ + "\n\n", ) ], + [mock.call(notices.Notice.CONTRACT_EXPIRED)], ), ], ) @@ -331,29 +472,30 @@ @mock.patch(M_PATH + "system.is_current_series_lts") @mock.patch(M_PATH + "ESMInfraEntitlement.application_status") @mock.patch(M_PATH + "system.is_current_series_active_esm") + @mock.patch(M_PATH + "notices.add") @mock.patch( M_PATH + "UAConfig.machine_token_file", new_callable=mock.PropertyMock ) @mock.patch(M_PATH + "system.write_file") @mock.patch(M_PATH + "system.ensure_file_absent") + @mock.patch(M_PATH + "notices.remove") @mock.patch(M_PATH + "update_contract_expiry") - @mock.patch("uaclient.contract.get_contract_expiry_status") @mock.patch(M_PATH + "_is_attached") def test_update_motd_messages( self, m_is_attached, - m_get_contract_expiry_status, m_update_contract_expiry, + m_notices_remove, m_ensure_file_absent, m_write_file, m_machine_token_file, + m_notices_add, m_is_current_series_active_esm, m_infra_status, m_is_current_series_lts, m_apps_status, m_api_updates_v1, - attached, - contract_expiry_statuses, + is_attached_side_effect, is_current_series_active_esm, infra_status, is_current_series_lts, @@ -361,12 +503,14 @@ updates, expected, update_contract_expiry_calls, + notices_remove_calls, ensure_file_absent_calls, write_file_calls, + notices_add_calls, FakeConfig, ): - m_is_attached.return_value = mock.MagicMock(is_attached=attached) - m_get_contract_expiry_status.side_effect = contract_expiry_statuses + + m_is_attached.side_effect = is_attached_side_effect m_is_current_series_active_esm.return_value = ( is_current_series_active_esm ) @@ -387,5 +531,7 @@ update_contract_expiry_calls == m_update_contract_expiry.call_args_list ) + assert notices_remove_calls == m_notices_remove.call_args_list assert ensure_file_absent_calls == m_ensure_file_absent.call_args_list assert write_file_calls == m_write_file.call_args_list + assert notices_add_calls == m_notices_add.call_args_list diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/timer/update_messaging.py ubuntu-advantage-tools-32~16.04/uaclient/timer/update_messaging.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/timer/update_messaging.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/timer/update_messaging.py 2024-04-23 13:37:02.000000000 +0000 @@ -14,10 +14,14 @@ from uaclient.api.u.pro.packages.updates.v1 import ( _updates as api_u_pro_packages_updates_v1, ) -from uaclient.api.u.pro.status.is_attached.v1 import _is_attached +from uaclient.api.u.pro.status.is_attached.v1 import ( + ContractExpiryStatus, + _is_attached, +) from uaclient.config import UAConfig from uaclient.entitlements import ESMAppsEntitlement, ESMInfraEntitlement from uaclient.entitlements.entitlement_status import ApplicationStatus +from uaclient.files import notices MOTD_CONTRACT_STATUS_FILE_NAME = "motd-contract-status" UPDATE_NOTIFIER_MOTD_SCRIPT = ( @@ -58,31 +62,36 @@ :param cfg: UAConfig instance for this environment. """ - if not _is_attached(cfg).is_attached: + is_attached_info = _is_attached(cfg) + if not is_attached_info.is_attached: return False - LOG.debug("Updating Ubuntu Pro messages for MOTD.") + LOG.info("Updating Ubuntu Pro messages for MOTD.") motd_contract_status_msg_path = os.path.join( cfg.data_dir, "messages", MOTD_CONTRACT_STATUS_FILE_NAME ) - expiry_status, remaining_days = contract.get_contract_expiry_status(cfg) + expiry_status = is_attached_info.contract_status + remaining_days = is_attached_info.contract_remaining_days + if expiry_status in ( - contract.ContractExpiryStatus.ACTIVE_EXPIRED_SOON, - contract.ContractExpiryStatus.EXPIRED_GRACE_PERIOD, - contract.ContractExpiryStatus.EXPIRED, + ContractExpiryStatus.ACTIVE_EXPIRED_SOON.value, + ContractExpiryStatus.EXPIRED_GRACE_PERIOD.value, + ContractExpiryStatus.EXPIRED.value, ): update_contract_expiry(cfg) - expiry_status, remaining_days = contract.get_contract_expiry_status( - cfg - ) + is_attached_info = _is_attached(cfg) + expiry_status = is_attached_info.contract_status + remaining_days = is_attached_info.contract_remaining_days if expiry_status in ( - contract.ContractExpiryStatus.ACTIVE, - contract.ContractExpiryStatus.NONE, + ContractExpiryStatus.ACTIVE.value, + ContractExpiryStatus.NONE.value, ): + notices.remove(notices.Notice.CONTRACT_EXPIRED) system.ensure_file_absent(motd_contract_status_msg_path) - elif expiry_status == contract.ContractExpiryStatus.ACTIVE_EXPIRED_SOON: + elif expiry_status == ContractExpiryStatus.ACTIVE_EXPIRED_SOON.value: + notices.remove(notices.Notice.CONTRACT_EXPIRED) system.write_file( motd_contract_status_msg_path, messages.CONTRACT_EXPIRES_SOON.pluralize(remaining_days).format( @@ -90,7 +99,8 @@ ) + "\n\n", ) - elif expiry_status == contract.ContractExpiryStatus.EXPIRED_GRACE_PERIOD: + elif expiry_status == ContractExpiryStatus.EXPIRED_GRACE_PERIOD.value: + notices.remove(notices.Notice.CONTRACT_EXPIRED) grace_period_remaining = ( defaults.CONTRACT_EXPIRY_GRACE_PERIOD_DAYS + remaining_days ) @@ -109,7 +119,9 @@ ) + "\n\n", ) - elif expiry_status == contract.ContractExpiryStatus.EXPIRED: + elif expiry_status == ContractExpiryStatus.EXPIRED.value: + notices.add(notices.Notice.CONTRACT_EXPIRED) + service = "n/a" pkg_num = 0 diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/util.py ubuntu-advantage-tools-32~16.04/uaclient/util.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/util.py 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/util.py 2024-04-23 13:37:02.000000000 +0000 @@ -7,7 +7,7 @@ import textwrap import time from functools import wraps -from typing import Any, Dict, List, Optional, Union # noqa: F401 +from typing import Any, Callable, Dict, List, Optional, Union # noqa: F401 from uaclient import exceptions, messages from uaclient.defaults import CONFIG_FIELD_ENVVAR_ALLOWLIST @@ -267,6 +267,7 @@ def handle_message_operations( msg_ops: Optional[MessagingOperations], + print_fn: Callable[[str], None], ) -> bool: """Emit messages to the console for user interaction @@ -277,15 +278,12 @@ :return: True upon success, False on failure. """ - from uaclient import event_logger - - event = event_logger.get_event_logger() if not msg_ops: return True for msg_op in msg_ops: if isinstance(msg_op, str): - event.info(msg_op) + print_fn(msg_op) else: # Then we are a callable and dict of args functor, args = msg_op if not functor(**args): @@ -466,10 +464,10 @@ return name + "." + new_extension -def print_package_list( +def create_package_list_str( package_list: List[str], ): - print( + return ( "\n".join( textwrap.wrap( " ".join(package_list), @@ -480,5 +478,5 @@ subsequent_indent=" ", ) ) + + "\n" ) - print("") diff -Nru ubuntu-advantage-tools-31.2.3~16.04/uaclient/version.py ubuntu-advantage-tools-32~16.04/uaclient/version.py --- ubuntu-advantage-tools-31.2.3~16.04/uaclient/version.py 2024-04-05 13:08:47.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/uaclient/version.py 2024-04-23 13:37:02.000000000 +0000 @@ -1,6 +1,7 @@ """ Client version related functions """ + import os.path from math import inf from typing import Optional @@ -14,7 +15,7 @@ from uaclient.exceptions import ProcessExecutionError from uaclient.system import subp -__VERSION__ = "31.2.3" +__VERSION__ = "32" PACKAGED_VERSION = "@@PACKAGED_VERSION@@" diff -Nru ubuntu-advantage-tools-31.2.3~16.04/ubuntu-advantage.1 ubuntu-advantage-tools-32~16.04/ubuntu-advantage.1 --- ubuntu-advantage-tools-31.2.3~16.04/ubuntu-advantage.1 2024-04-02 16:56:41.000000000 +0000 +++ ubuntu-advantage-tools-32~16.04/ubuntu-advantage.1 2024-04-23 13:37:02.000000000 +0000 @@ -53,7 +53,7 @@ Create a tarball with all relevant logs and debug data. The \fI--output\fR parameter defines the path to the tarball. If not -provided, the file is saved as \fBua_logs.tar.gz\fP in the current +provided, the file is saved as \fBpro_logs.tar.gz\fP in the current directory. .TP