diff --git a/.cockpit-ci/run b/.cockpit-ci/run index 2400c9fa6e..dc220f39bd 120000 --- a/.cockpit-ci/run +++ b/.cockpit-ci/run @@ -1 +1 @@ -../integration-tests/run \ No newline at end of file +../cockpit-tests/run \ No newline at end of file diff --git a/.fmf/version b/.fmf/version new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/.fmf/version @@ -0,0 +1 @@ +1 diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml deleted file mode 100644 index 52918bb809..0000000000 --- a/.github/workflows/auto-assign.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: AutoAssignReviewer - -on: - pull_request: - types: [opened, ready_for_review] - -jobs: - auto-assign-reviewer: - runs-on: ubuntu-latest - steps: - - name: Run assignment of reviewer team - uses: nikosmoum/auto-assign-reviewer-team@v0.5 - with: - githubToken: ${{ secrets.AUTOASSIGNREVIEWERSECRET }} - teamName: 'client-tools' - diff --git a/.github/workflows/libdnf.yml b/.github/workflows/libdnf.yml index 91425b0bec..91bc3989a8 100644 --- a/.github/workflows/libdnf.yml +++ b/.github/workflows/libdnf.yml @@ -13,7 +13,7 @@ jobs: matrix: include: - name: "CentOS Stream 10" - image: "quay.io/centos/centos:stream10-development" + image: "quay.io/centos/centos:stream10" - name: "Fedora latest" image: "registry.fedoraproject.org/fedora:latest" - name: "Fedora Rawhide" diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index a5bfd4e4c8..c0d23dc859 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -17,7 +17,7 @@ jobs: matrix: include: - name: "CentOS Stream 10" - image: "quay.io/centos/centos:stream10-development" + image: "quay.io/centos/centos:stream10" pytest_args: '' - name: "Fedora latest" image: "registry.fedoraproject.org/fedora:latest" diff --git a/.github/workflows/tito.yml b/.github/workflows/tito.yml index dd5663c720..1c882fb39c 100644 --- a/.github/workflows/tito.yml +++ b/.github/workflows/tito.yml @@ -12,9 +12,6 @@ jobs: fail-fast: false matrix: include: - - name: "CentOS Stream 10" - image: "quay.io/centos/centos:stream10-development" - packager: "dnf4" - name: "Fedora latest" image: "registry.fedoraproject.org/fedora:latest" packager: "dnf4" diff --git a/.packit.yml b/.packit.yml new file mode 100644 index 0000000000..2f7be44275 --- /dev/null +++ b/.packit.yml @@ -0,0 +1,46 @@ +upstream_package_name: subscription-manager +downstream_package_name: subscription-manager +specfile_path: subscription-manager.spec +upstream_tag_template: "subscription-manager-{version}-1" + +jobs: + - job: copr_build + trigger: pull_request + targets: + - centos-stream-10 + - fedora-all + + - job: copr_build + trigger: commit + branch: main + owner: "@yggdrasil" + project: latest + targets: + - centos-stream-10 + - fedora-all + +# - job: tests +# trigger: pull_request +# identifier: "unit/centos-stream" +# targets: +# - centos-stream-10 +# labels: +# - unit +# tf_extra_params: +# environments: +# - artifacts: +# - type: repository-file +# id: https://copr.fedorainfracloud.org/coprs/g/yggdrasil/latest/repo/centos-stream-$releasever/group_yggdrasil-latest-centos-stream-$releasever.repo + + - job: tests + trigger: pull_request + identifier: "unit/fedora" + targets: + - fedora-all + labels: + - unit + tf_extra_params: + environments: + - artifacts: + - type: repository-file + id: https://copr.fedorainfracloud.org/coprs/g/yggdrasil/latest/repo/fedora-$releasever/group_yggdrasil-latest-fedora-$releasever.repo diff --git a/.tito/packages/subscription-manager b/.tito/packages/subscription-manager index a7481c00e4..552dd25d20 100644 --- a/.tito/packages/subscription-manager +++ b/.tito/packages/subscription-manager @@ -1 +1 @@ -1.29.40-1 ./ +1.30.3-1 ./ diff --git a/INSTALL.md b/INSTALL.md index e261af39ab..09a6962927 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -9,10 +9,17 @@ To install subscription-manager, run sudo dnf install subscription-manager ``` +RHEL already comes with `subscription-manager` pre-installed. With `subscription-manager` present, you can get register the system with your Red Hat account by running + +```bash +sudo subscription-manager register +``` + + ## Developer installation -The process below has been tested on Fedora 36. -Other Fedora versions or distributions may require some adaptation. +The process below has been tested on Fedora 36 and RHEL 9. +Other versions or distributions may require some adaptation. 1. First you need to install RPM packages to build and run subscription-manager binaries: @@ -23,9 +30,15 @@ Other Fedora versions or distributions may require some adaptation. -2. Install Fedora's subscription-manager RPM and packages required to run the test suite: + On RHEL, you need to enable the [CodeReady Linux Builder](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/package_manifest/repositories#CodeReadyLinuxBuilder-repository) repository in order to gain access to the development packages like `libdnf-devel`. + + ```bash + sudo subscription-manager repos --enable codeready-builder-for-rhel-9-x86_64-rpms + ``` + +2. Install the `subscription-manager` RPM (not required for RHEL) and packages required to run the test suite: - ***NOTE**: Installing `subscription-manager` package is strictly not necessary, but it will pull down all dependencies and create all the files used by subscription-manager.* + ***NOTE**: Installing `subscription-manager` package is not strictly necessary even on Fedora, but it will pull down all dependencies and create all the files used by subscription-manager.* ```bash sudo dnf install --setopt install_weak_deps=False subscription-manager \ @@ -87,7 +100,7 @@ Other Fedora versions or distributions may require some adaptation. alias rhsmcertd="sudo \ PYTHONPATH=/path/to/subscription-manager/src \ $(which python3) \ - -m subscription_manager.scripts.rhsmcertd_worker --autoheal" + -m subscription_manager.scripts.rhsmcertd_worker" ``` Before you run rhsm service manually, ensure you have disabled the system service first: diff --git a/Makefile b/Makefile index 35763d6703..2da843d702 100644 --- a/Makefile +++ b/Makefile @@ -237,6 +237,10 @@ install-files: dbus-install install-conf install-plugins install -m 644 etc-conf/subscription-manager.conf.tmpfiles \ $(DESTDIR)/$(PREFIX)/lib/tmpfiles.d/subscription-manager.conf + # Install configuration file with system users and group (only rhsm group ATM) + install -d $(DESTDIR)/$(PREFIX)/lib/sysusers.d + install -m 644 etc-conf/rhsm-sysuser.conf $(DESTDIR)/$(PREFIX)/lib/sysusers.d/rhsm.conf + # SUSE Linux does not make use of consolehelper if [ -f /etc/redhat-release ]; then \ ln -sf /usr/bin/consolehelper $(DESTDIR)/$(PREFIX)/bin/subscription-manager; \ diff --git a/TESTING.md b/TESTING.md index 147f4965cf..2a0ce88e7d 100644 --- a/TESTING.md +++ b/TESTING.md @@ -2,6 +2,12 @@ ## subscription-manager +First, install the tests dependencies from [`test-requirements.txt`](test-requirements.txt). Make sure your virtual environment is activated. + +```bash +pip install -r test-requirements.txt +``` + ```bash pytest # or, for increased verbosity diff --git a/integration-tests/run b/cockpit-tests/run similarity index 86% rename from integration-tests/run rename to cockpit-tests/run index a414cc413f..a442cb3e0d 100755 --- a/integration-tests/run +++ b/cockpit-tests/run @@ -6,8 +6,8 @@ # like this: # # $ export TEST_OS=rhel-8-4 -# $ ./integration-tests/run prepare -# $ ./integration-tests/check-subscriptions -v -j1 -t +# $ ./cockpit-tests/run prepare +# $ ./cockpit-tests/check-subscriptions -v -j1 -t # # You need a couple of things installed for that. At least Python 3, # a Chromium browser, and libvirtd. See @@ -20,7 +20,7 @@ ifeq ($(TEST_OS),) TEST_OS = rhel-8-4 endif export TEST_OS -SUB_MAN_COCKPIT=$(CURDIR)/integration-tests/submancockpit +SUB_MAN_COCKPIT=$(CURDIR)/cockpit-tests/submancockpit VM_IMAGE=$(SUB_MAN_COCKPIT)/test/images/$(TEST_OS).qcow2 SUBMAN_TAR=$(CURDIR)/dist/subscription-manager.tar.gz SMBEXT_TAR=$(CURDIR)/dist/subscription-manager-build-extra.tar.gz @@ -45,7 +45,7 @@ $(SUBMAN_TAR): $(SMBEXT_TAR): tar czf $(SMBEXT_TAR) build_ext -$(VM_IMAGE): $(SUB_MAN_COCKPIT) $(SUBMAN_TAR) $(SMBEXT_TAR) integration-tests/vm.install +$(VM_IMAGE): $(SUB_MAN_COCKPIT) $(SUBMAN_TAR) $(SMBEXT_TAR) cockpit-tests/vm.install rm -f $(VM_IMAGE) # setup the test image from subscription-manager-cockpit without # a custom subscription-manager, which will be installed manually @@ -54,7 +54,7 @@ $(VM_IMAGE): $(SUB_MAN_COCKPIT) $(SUBMAN_TAR) $(SMBEXT_TAR) integration-tests/vm cd $(SUB_MAN_COCKPIT) && bots/image-customize -v $(TEST_OS) \ -u $(SUBMAN_TAR):/var/tmp/ \ -u $(SMBEXT_TAR):/var/tmp/ \ - -s ../../integration-tests/vm.install + -s ../../cockpit-tests/vm.install $(SUB_MAN_COCKPIT): git clone --quiet https://github.com/candlepin/subscription-manager-cockpit.git $(SUB_MAN_COCKPIT) diff --git a/integration-tests/vm.install b/cockpit-tests/vm.install similarity index 100% rename from integration-tests/vm.install rename to cockpit-tests/vm.install diff --git a/debian-stuff/80package-profile-upload b/debian-stuff/80package-profile-upload index a188da5d57..f64aecd377 100644 --- a/debian-stuff/80package-profile-upload +++ b/debian-stuff/80package-profile-upload @@ -1 +1 @@ -DPkg::Post-Invoke { /usr/lib/katello-client/bin/deb_package_profile_upload } +DPkg::Post-Invoke { /usr/bin/package-profile-upload } diff --git a/debian-stuff/deb_package_profile_upload b/debian-stuff/deb_package_profile_upload deleted file mode 100755 index b2acee8ffc..0000000000 --- a/debian-stuff/deb_package_profile_upload +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh - -set -eu - -exit_msg() { - echo $1 - exit -} - -[ -f /etc/rhsm/rhsm.conf ] || exit_msg "subscription-manager is not installed. Skipping." - -KATELLO_SERVER=$(sed -n -e 's/^hostname\s*=\s*\(.*\)$/\1/p' /etc/rhsm/rhsm.conf) - -[ "${KATELLO_SERVER}" ] || exit_msg "No Katello server is configured. Skipping." - -IDENTITY=$(LANG=C subscription-manager identity 2> /dev/null | sed -n 's/system identity: //p') - -[ "${IDENTITY}" ] || exit_msg "Host is not registered with Katello. Skipping." - -echo -n "Upload Package Profile" - -dpkg -l |\ - awk '$1 ~ /^ii/ { sub(/:.*$/, "", $2); printf("{\"name\": \"%s\", \"version\": \"%s\", \"architecture\": \"%s\"}\n", $2, $3, $4) }' |\ - jq -s '{"deb_package_profile": {"deb_packages": .}}' |\ - http --cert /etc/pki/consumer/cert.pem --cert-key /etc/pki/consumer/key.pem --verify /etc/rhsm/ca/katello-default-ca.pem PUT https://${KATELLO_SERVER}/rhsm/systems/${IDENTITY}/deb_package_profile/ > /dev/null || exit_msg "Something went wrong." - -echo . - -if [ -f /etc/apt/sources.list.d/rhsm.sources ] -then - echo -n "Update bound repositories" - sed -ne 's/^baseurl:\s*\(.*\)$/"\1"/p' < /etc/apt/sources.list.d/rhsm.sources | jq -s "{enabled_repos: {repos: map({baseurl: [.]})}}" |\ - http --cert /etc/pki/consumer/cert.pem --cert-key /etc/pki/consumer/key.pem --verify /etc/rhsm/ca/katello-default-ca.pem PUT https://${KATELLO_SERVER}/rhsm/systems/${IDENTITY}/enabled_repos/ > /dev/null || exit_msg "Something went wrong." - echo . -fi diff --git a/debian-stuff/katello b/debian-stuff/katello index 51f6ded754..b2abc67875 100755 --- a/debian-stuff/katello +++ b/debian-stuff/katello @@ -1,4 +1,4 @@ -#! /usr/bin/python -u +#! /usr/bin/python3 -u # # The katello Acquire Method # @@ -29,7 +29,7 @@ import re import hashlib import requests from urllib.parse import quote -from configparser import SafeConfigParser +from configparser import ConfigParser class pkg_acquire_method: @@ -138,7 +138,7 @@ class katello_method(pkg_acquire_method): Return the proxy server config from /etc/rhsm/rhsm.conf as dict Adapted from https://github.com/Katello/katello-client-bootstrap/blob/master/bootstrap.py """ - rhsmconfig = SafeConfigParser() + rhsmconfig = ConfigParser() rhsmconfig.read('/etc/rhsm/rhsm.conf') proxy_options = [option for option in rhsmconfig.options('server') if option.startswith('proxy')] diff --git a/etc-conf/dbus/system.d/com.redhat.RHSM1.conf b/etc-conf/dbus/system.d/com.redhat.RHSM1.conf index 4c04437aa9..33f60e13a0 100644 --- a/etc-conf/dbus/system.d/com.redhat.RHSM1.conf +++ b/etc-conf/dbus/system.d/com.redhat.RHSM1.conf @@ -14,9 +14,6 @@ - - diff --git a/etc-conf/rhsm-sysuser.conf b/etc-conf/rhsm-sysuser.conf new file mode 100644 index 0000000000..103b9c7d6c --- /dev/null +++ b/etc-conf/rhsm-sysuser.conf @@ -0,0 +1,2 @@ +#Type Name ID GECOS Home directory Shell +g rhsm - - \ No newline at end of file diff --git a/etc-conf/rhsm.conf b/etc-conf/rhsm.conf index 5fec341697..b0553f2ac4 100644 --- a/etc-conf/rhsm.conf +++ b/etc-conf/rhsm.conf @@ -87,8 +87,6 @@ progress_messages = 1 [rhsmcertd] # Interval to run cert check (in minutes): certCheckInterval = 240 -# Interval to run auto-attach (in minutes): -autoAttachInterval = 1440 # If set to zero, the checks done by the rhsmcertd daemon will not be splayed (randomly offset) splay = 1 # If set to 1, rhsmcertd will not execute. diff --git a/etc-conf/rhsmcertd.completion.sh b/etc-conf/rhsmcertd.completion.sh index 24e0e2a2ce..95a74aea90 100644 --- a/etc-conf/rhsmcertd.completion.sh +++ b/etc-conf/rhsmcertd.completion.sh @@ -11,7 +11,7 @@ _rhsmcertd() first="${COMP_WORDS[1]}" cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - opts="-h --help -c --cert-check-interval --cert-interval -d --debug --heal-interval -i --auto-attach-interval -n --now -s --no-splay -a --auto-registration -r --auto-registration-interval" + opts="-h --help -c --cert-check-interval -d --debug -n --now -s --no-splay -a --auto-registration -r --auto-registration-interval" case "${cur}" in -*) diff --git a/etc-conf/subscription-manager.completion.sh b/etc-conf/subscription-manager.completion.sh index f5594437d7..c9dca88edd 100644 --- a/etc-conf/subscription-manager.completion.sh +++ b/etc-conf/subscription-manager.completion.sh @@ -10,35 +10,11 @@ _subscription_manager_common_opts="--proxy --proxyuser --proxypassword --noproxy _subscription_manager_common_url_opts="--insecure --serverurl" # complete functions for subcommands ($1 - current opt, $2 - previous opt) -_subscription_manager_auto_attach() -{ - local opts="--enable --disable --show ${_subscription_manager_common_opts}" - COMPREPLY=($(compgen -W "${opts}" -- ${1})) -} - -_subscription_manager_attach() -{ - # try to autocomplete pool id's as well - # doesn't work well with sudo/non root users though - case $prev in - --pool) - # wee bit of a hack to handle that we can't actually run subscription-manager list --available unless - # we are root. try it directly (as opposed to userhelper links) and if it fails,ignore it - POOLS=$(LANG=C /usr/sbin/subscription-manager list --available 2>/dev/null | sed -ne "s|Pool ID:\s*\(\S*\)|\1|p" ) - COMPREPLY=($(compgen -W "${POOLS}" -- ${1})) - return 0 - esac - local opts="--auto --pool --quantity --servicelevel --file - ${_subscription_manager_common_opts}" - COMPREPLY=($(compgen -W "${opts}" -- ${1})) -} - _subscription_manager_syspurpose() { - local opts="addons role service-level usage --show ${_subscription_manager_common_opts}" + local opts="role service-level usage --show ${_subscription_manager_common_opts}" case "${2}" in - addons|\ role|\ usage) "_subscription_manager_$2" "${1}" "${2}" @@ -59,7 +35,7 @@ _subscription_manager_syspurpose() _subscription_manager_role() { local opts="--list --org --set --show - --unset --username --password --token + --unset --username --password ${_subscription_manager_common_opts}" COMPREPLY=($(compgen -W "${opts}" -- ${1})) } @@ -67,46 +43,17 @@ _subscription_manager_role() _subscription_manager_usage() { local opts="--list --org --set --show - --unset --username --password --token - ${_subscription_manager_common_opts}" - COMPREPLY=($(compgen -W "${opts}" -- ${1})) -} - -_subscription_manager_addons() -{ - local opts="--list --org --show --add --remove - --unset --username --password --token + --unset --username --password ${_subscription_manager_common_opts}" COMPREPLY=($(compgen -W "${opts}" -- ${1})) } - _subscription_manager_unregister() { local opts="${_subscription_manager_common_opts}" COMPREPLY=($(compgen -W "${opts}" -- ${1})) } -_subscription_manager_remove() -{ - # try to autocomplete serial number as well - case $prev in - --serial) - SERIALS=$(LANG=C /usr/sbin/subscription-manager list --consumed 2>/dev/null | sed -ne "s|Serial:\s*\(\S*\)|\1|p" ) - COMPREPLY=($(compgen -W "${SERIALS}" -- ${1})) - return 0 - ;; - --pool) - POOLS=$(LANG=C /usr/sbin/subscription-manager list --consumed 2>/dev/null | sed -ne "s|Pool ID:\s*(\S*\)|\1|p" ) - COMPREPLY=($(compgen -W "${POOLS}" -- ${1})) - return 0 - ;; - esac - local opts="--serial --pool --all - ${_subscription_manager_common_opts}" - COMPREPLY=($(compgen -W "${opts}" -- ${1})) -} - _subscription_manager_clean() { local opts="-h --help" @@ -125,7 +72,7 @@ _subscription_manager_config() _subscription_manager_environments() { - local opts="--org --password --username --token --set --list --list-enabled --list-disabled + local opts="--org --password --username --set --list --list-enabled --list-disabled ${_subscription_manager_common_url_opts} ${_subscription_manager_common_opts}" COMPREPLY=($(compgen -W "${opts}" -- ${1})) @@ -140,25 +87,22 @@ _subscription_manager_facts() _subscription_manager_identity() { - local opts="--force --password --regenerate --username --token + local opts="--force --password --regenerate --username ${_subscription_manager_common_opts}" COMPREPLY=($(compgen -W "${opts}" -- ${1})) } _subscription_manager_list() { - local opts="--afterdate --all --available --consumed --installed - --ondate --servicelevel - --match-installed --no-overlap + local opts="--installed --matches - --pool-only ${_subscription_manager_common_opts}" COMPREPLY=($(compgen -W "${opts}" -- ${1})) } _subscription_manager_orgs() { - local opts="--password --username --token + local opts="--password --username ${_subscription_manager_common_url_opts} ${_subscription_manager_common_opts}" COMPREPLY=($(compgen -W "${opts}" -- ${1})) @@ -178,13 +122,6 @@ _subscription_manager_plugins() COMPREPLY=($(compgen -W "${opts}" -- ${1})) } -_subscription_manager_redeem() -{ - local opts="--email --locale - ${_subscription_manager_common_opts}" - COMPREPLY=($(compgen -W "${opts}" -- ${1})) -} - _subscription_manager_refresh() { local opts="--force @@ -195,9 +132,9 @@ _subscription_manager_refresh() _subscription_manager_register() { - local opts="--activationkey --auto-attach --autosubscribe --baseurl --consumerid + local opts="--activationkey --baseurl --consumerid --environments --force --name --org --password --release - --servicelevel --username --token + --username ${_subscription_manager_common_url_opts} ${_subscription_manager_common_opts}" COMPREPLY=($(compgen -W "${opts}" -- ${1})) @@ -222,7 +159,7 @@ _subscription_manager_repos() _subscription_manager_service_level() { local opts="--list --org --set --show - --unset --username --password --token + --unset --username --password ${_subscription_manager_common_url_opts} ${_subscription_manager_common_opts}" COMPREPLY=($(compgen -W "${opts}" -- ${1})) @@ -260,8 +197,8 @@ _subscription_manager() done # top-level commands and options - opts="attach auto-attach clean config environments facts identity list orgs - repo-override plugins redeem refresh register release remove repos status + opts="clean config environments facts identity list orgs + repo-override plugins refresh register release repos status syspurpose unregister version ${_subscription_manager_help_opts}" case "${first}" in @@ -273,7 +210,6 @@ _subscription_manager() list|\ orgs|\ plugins|\ - redeem|\ refresh|\ register|\ release|\ @@ -285,18 +221,6 @@ _subscription_manager() "_subscription_manager_$first" "${cur}" "${prev}" return 0 ;; - attach) - "_subscription_manager_attach" "${cur}" "${prev}" - return 0 - ;; - remove) - "_subscription_manager_remove" "${cur}" "${prev}" - return 0 - ;; - auto-attach) - "_subscription_manager_auto_attach" "${cur}" "${prev}" - return 0 - ;; repo-override) "_subscription_manager_repo_override" "${cur}" "${prev}" return 0 diff --git a/example-plugins/dbus_event.py b/example-plugins/dbus_event.py index 8d2d54530c..175b1d915a 100644 --- a/example-plugins/dbus_event.py +++ b/example-plugins/dbus_event.py @@ -89,9 +89,3 @@ def pre_subscribe_hook(self, conduit): def post_subscribe_hook(self, conduit): self._dbus_event("post_subscribe", conduit) - - def pre_auto_attach_hook(self, conduit): - self._dbus_event("pre_auto_attach", conduit) - - def post_auto_attach_hook(self, conduit): - self._dbus_event("post_auto_attach", conduit) diff --git a/example-plugins/subscribe.py b/example-plugins/subscribe.py index 7fe604993c..08ac839af8 100644 --- a/example-plugins/subscribe.py +++ b/example-plugins/subscribe.py @@ -37,9 +37,3 @@ def post_subscribe_hook(self, conduit): conduit: A PostSubscriptionConduit() """ conduit.log.debug("post subscribe called") - - def pre_auto_attach_hook(self, conduit): - conduit.log.debug("pre auto attach called") - - def post_auto_attach_hook(self, conduit): - conduit.log.debug("post auto attach called") diff --git a/integration-tests/README.md b/integration-tests/README.md new file mode 100644 index 0000000000..e206ade893 --- /dev/null +++ b/integration-tests/README.md @@ -0,0 +1,146 @@ +# Integration Test for subscription-manager + +There are integration tests for all parts of subscription-manager +in this directory. + +DBus tests are presented currently - they verify DBus api of *rhsm.service* +see [DBus objects](https://www.candlepinproject.org/docs/subscription-manager/dbus_objects.html) + +The tests use pytest ecosystem. + +## Installation + +1) Run local candlepin + +```shell +podman run -d --name canlepin -p 8080:8080 -p 8443:8443 --hostname candlepin.local ghcr.io/ptoscano/candlepin-unofficial:latest +``` + +2) Create additional testing data in candlepin + +Environments for *donaldduck* organization + +``` +curl --stderr /dev/null --insecure --user admin:admin --request POST \ +--data '{"id": "env-id-1", "name": "env-name-1", "description": "Testing environment num. 1"}' \ +--header 'accept: application/json' --header 'content-type: application/json' \ +https://localhost:8443/candlepin/owners/donaldduck/environments + +curl --stderr /dev/null --insecure --user admin:admin --request POST \ +--data '{"id": "env-id-2", "name": "env-name-2", "description": "Testing environment num. 2"}' \ +--header 'accept: application/json' --header 'content-type: application/json' \ +https://localhost:8443/candlepin/owners/donaldduck/environments +``` + +> citation from 'man subscription-manager' +> With on-premise subscription services, such as Subscription Asset +> Manager, the infrastructure is more complex. The local +> administrator can define independent groups called organizations +> which represent physical or organizational divisions (--org). +> Those organizations can be subdivided into environments. + +Activation keys for *donaldduck* organization + +> The tests use already installed test activation keys +> They are: +> - *default_key* +> - *awesome_os_pool" + +## Configuration + +Tests use [Dynaconf](https://www.dynaconf.com/) to load config +values. + +They are stored in a file in this directory *settings.toml* + +Config values for _testing_ environment + +```yaml +[testing] +candlepin.host = "localhost" +candlepin.port = 8443 +candlepin.insecure = true +candlepin.prefix = "/candlepin" +candlepin.username = "duey" +candlepin.password = "password" +candlepin.org = "donaldduck" +candlepin.activation_keys = ["default_key","awesome_os_pool"] +candlepin.environment.names = ["env-name-01","env-name-02"] +candlepin.environment.ids = ["env-id-01","env-id-02"] + +insights.legacy_upload = false +console.host = "cert.console.redhat.com" + +auth_proxy.host = +auth_proxy.port = 3127 +auth_proxy.username = "redhat" +auth_proxy.password = "redhat" + +noauth_proxy.host = +noauth_proxy.port = 3129 + +insights.hbi_host = "cert.console.redhat.com" +``` + +Configuration for pytest + +> There is a file *pytest.ini* in the main directory of this repo. +> It has nothing to do with integration-tests. It is a confiuration +> for unittests. + +*integration-tests/pytest.ini* + +```ini +[pytest] +addopts = "-srxv --capture=sys" +testpaths = "./" +log_cli = true +log_level = INFO +``` + +## Python virtual environment for testing + +It is good practice to use python virtual environment to run the +tests. All required packages for pytest are stored in +*requirements.txt*. + +> There is a file *requirements.txt* in the main directory of the +> repo. It is used by unittests. I has nothing to do with +> integration-tests at all. + +```shell +cd integration-tests +python3 -mvenv venv +source venv +pip install -r requirements.txt +deactivate +``` + +## Running the tests + +```shell +cd integration-tests +source venv +pytest +deativate +``` + +> There is a nice help for pytest in [Testing](../TESTING.md). It is +> full of interesting hits to run just a few tests, to increase output +> of a test run ... + +### Runnning integration tests using tmt + +You can use [Testing Farm](https://docs.testing-farm.io/Testing%20Farm/0.1/index.html) +to run the tests. + +It suposes that the package *subscription-manager* is installed at a local box. + +```shell +cd subscription-manager +sudo tmt --feeling-safe run -vvv --all provision --how local +``` + +> All details for tmt to run are stored at directory *systemtest* +> It is a starting point for deeper investigation to understand how +> the tests are run using tmt. diff --git a/integration-tests/__init__.py b/integration-tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/integration-tests/conftest.py b/integration-tests/conftest.py new file mode 100644 index 0000000000..ece0bd501b --- /dev/null +++ b/integration-tests/conftest.py @@ -0,0 +1,47 @@ +import logging +import os + +from dasbus.connection import MessageBus +from gi.repository import Gio + +import gi + +gi.require_version("Gio", "2.0") + +logger = logging.getLogger(__name__) + + +class RHSMPrivateBus(MessageBus): + """Representation of RHSM private bus connection that can be used as a context manager.""" + + def __init__(self, rhsm_register_server_proxy, *args, **kwargs): + """Representation of RHSM private bus connection that can be used as a context manager. + + :param rhsm_register_server_proxy: DBus proxy for the RHSM RegisterServer object + """ + super().__init__(*args, **kwargs) + self._rhsm_register_server_proxy = rhsm_register_server_proxy + self._private_bus_address = None + + def __enter__(self): + logger.debug("subscription: starting RHSM private DBus session") + locale = os.environ.get("LANG", "") + self._private_bus_address = self._rhsm_register_server_proxy.Start(locale) + logger.debug("subscription: RHSM private DBus session has been started") + return self + + def __exit__(self, _exc_type, _exc_value, _exc_traceback): + logger.debug("subscription: shutting down the RHSM private DBus session") + self.disconnect() + locale = os.environ.get("LANG", "") + self._rhsm_register_server_proxy.Stop(locale) + logger.debug("subscription: RHSM private DBus session has been shutdown") + + def _get_connection(self): + """Get a connection to RHSM private DBus session.""" + # the RHSM private bus address is potentially sensitive + # so we will not log it + logger.info("Connecting to the RHSM private DBus session.") + return self._provider.get_addressed_bus_connection( + bus_address=self._private_bus_address, flags=Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT + ) diff --git a/integration-tests/constants.py b/integration-tests/constants.py new file mode 100644 index 0000000000..20de67dd35 --- /dev/null +++ b/integration-tests/constants.py @@ -0,0 +1,25 @@ +from dasbus.identifier import DBusObjectIdentifier, DBusServiceIdentifier +from dasbus.connection import SystemMessageBus + + +HOST_DETAILS: str = "/var/lib/insights/host-details.json" +MACHINE_ID_FILE: str = "/etc/insights-client/machine-id" +RHSM_CONFIG_FILE_PATH: str = "/etc/rhsm/rhsm.conf" + +RHSM_NAMESPACE = ("com", "redhat", "RHSM1") + +RHSM = DBusServiceIdentifier(namespace=RHSM_NAMESPACE, message_bus=SystemMessageBus()) + +RHSM_CONFIG = DBusObjectIdentifier(namespace=RHSM_NAMESPACE, basename="Config") + +RHSM_REGISTER_SERVER = DBusObjectIdentifier(namespace=RHSM_NAMESPACE, basename="RegisterServer") + +RHSM_REGISTER = DBusObjectIdentifier(namespace=RHSM_NAMESPACE, basename="Register") + +RHSM_UNREGISTER = DBusObjectIdentifier(namespace=RHSM_NAMESPACE, basename="Unregister") + +RHSM_ENTITLEMENT = DBusObjectIdentifier(namespace=RHSM_NAMESPACE, basename="Entitlement") + +RHSM_SYSPURPOSE = DBusObjectIdentifier(namespace=RHSM_NAMESPACE, basename="Syspurpose") + +RHSM_CONSUMER = DBusObjectIdentifier(namespace=RHSM_NAMESPACE, basename="Consumer") diff --git a/integration-tests/pytest.ini b/integration-tests/pytest.ini new file mode 100644 index 0000000000..cc5ed0d2da --- /dev/null +++ b/integration-tests/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +addopts = "-srxv -vvv --capture=sys" +testpaths = "./" +log_cli = true +log_level = DEBUG diff --git a/integration-tests/requirements.txt b/integration-tests/requirements.txt new file mode 100644 index 0000000000..448eebeefc --- /dev/null +++ b/integration-tests/requirements.txt @@ -0,0 +1,14 @@ +# the version of black is specified also in the stylish.yml github workflow; +# please update the version there in case it is bumped here +black==24.3.0 +flake8 +git+https://github.com/ptoscano/pytest-client-tools@main +pytest +pyyaml +simplejson +dasbus +pycairo +PyGObject +sh +iniparse +funcy diff --git a/integration-tests/scripts/post-environments.sh b/integration-tests/scripts/post-environments.sh new file mode 100755 index 0000000000..830e473485 --- /dev/null +++ b/integration-tests/scripts/post-environments.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +USERNAME=admin +PASSWORD=admin +ORG=donaldduck + + +# The script will create two environments for an organization. +# +# citation from 'man subscription-manager': +# +# With on-premise subscription services, such as Subscription Asset Manager, +# the infrastructure is more complex. The local administrator can define +# independent groups called organizations which represent physical +# or organizational divisions (--org). Those organizations can be subdivided +# into environments (--environment). +# + +curl -k --request POST --user ${USERNAME}:${PASSWORD} \ + --data '{"id": "env-id-01", "name": "env-name-01", "description": "Testing environment num. 1"}' \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + https://localhost:8443/candlepin/owners/${ORG}/environments + +curl -k --request POST --user ${USERNAME}:${PASSWORD} \ + --data '{"id": "env-id-02", "name": "env-name-02", "description": "Testing environment num. 2", "type": "content-template"}' \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + https://localhost:8443/candlepin/owners/${ORG}/environments + diff --git a/integration-tests/scripts/run-local-candlepin.sh b/integration-tests/scripts/run-local-candlepin.sh new file mode 100755 index 0000000000..b8e19d533e --- /dev/null +++ b/integration-tests/scripts/run-local-candlepin.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# +# A local candlepin is used for most of integration tests. +# It is fast to run the tests and the candlepin comes with testing data. +# So the environment is mostly prepared in the candlepin after we run a contianer. +# +# For most information see https://github.com/ptoscano/candlepin-container-unofficial +# +podman run -d --name candlepin -p 8080:8080 -p 8443:8443 --hostname candlepin.local ghcr.io/ptoscano/candlepin-unofficial:latest diff --git a/integration-tests/test_register.py b/integration-tests/test_register.py new file mode 100644 index 0000000000..4994a6d3e4 --- /dev/null +++ b/integration-tests/test_register.py @@ -0,0 +1,137 @@ +# Copyright (c) 2024 Red Hat, Inc. +# +# This software is licensed to you under the GNU General Public +# License as published by the Free Software Foundation; either version +# 2 of the License (GPLv2) or (at your option) any later version. +# There is NO WARRANTY for this software, express or implied, +# including the implied warranties of MERCHANTABILITY, +# NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should +# have received a copy of GPLv2 along with this software; if not, see +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +# + +import pytest +import json +from conftest import RHSMPrivateBus +from constants import RHSM, RHSM_REGISTER_SERVER, RHSM_REGISTER +from dasbus.error import DBusError +from dasbus.typing import get_variant, Str +from funcy import partial + +import logging + +logger = logging.getLogger(__name__) + +""" +Integration test for DBus RHSM Register Object. + +See https://www.candlepinproject.org/docs/subscription-manager/dbus_objects.html#register +for more details. + +Main usecases are presented in this file. + +Special usecases for registering (with proxy, activation keys, ...) are presented +in its own files. + +It is important to run tests as root. Since RegisterServer is a system dbus service. +And it provides a unix socket connection. +""" + +# each call uses standard english locale +locale = "en_US.UTF-8" + + +def test_register(any_candlepin, subman, test_config): + """ + https://www.candlepinproject.org/docs/subscription-manager/dbus_objects.html#methods-6 + """ + assert not subman.is_registered + proxy = RHSM.get_proxy(RHSM_REGISTER_SERVER) + with RHSMPrivateBus(proxy) as private_bus: + private_proxy = private_bus.get_proxy(RHSM.service_name, RHSM_REGISTER.object_path) + response = private_proxy.Register( + "", + test_config.get("candlepin", "username"), + test_config.get("candlepin", "password"), + {}, + {}, + locale, + ) + response_data = json.loads(response) + assert "idCert" in response_data, "A response contains of consumer certificate" + assert frozenset(["key", "cert", "updated", "created", "id", "serial"]).issubset( + frozenset(response_data["idCert"].keys()) + ) + + assert subman.is_registered + + +def test_register_with_org(external_candlepin, subman, test_config): + """ + https://www.candlepinproject.org/docs/subscription-manager/dbus_objects.html#methods-6 + """ + assert not subman.is_registered + proxy = RHSM.get_proxy(RHSM_REGISTER_SERVER) + with RHSMPrivateBus(proxy) as private_bus: + private_proxy = private_bus.get_proxy(RHSM.service_name, RHSM_REGISTER.object_path) + private_proxy.Register( + test_config.get("candlepin", "org"), + test_config.get("candlepin", "username"), + test_config.get("candlepin", "password"), + {}, + {}, + locale, + ) + assert subman.is_registered + + +@pytest.mark.parametrize("enable_content", ["true", "false"]) +def test_register_with_enable_content(external_candlepin, subman, test_config, enable_content): + """ + https://www.candlepinproject.org/docs/subscription-manager/dbus_objects.html#methods-6 + """ + assert not subman.is_registered + proxy = RHSM.get_proxy(RHSM_REGISTER_SERVER) + with RHSMPrivateBus(proxy) as private_bus: + private_proxy = private_bus.get_proxy(RHSM.service_name, RHSM_REGISTER.object_path) + private_proxy.Register( + test_config.get("candlepin", "org"), + test_config.get("candlepin", "username"), + test_config.get("candlepin", "password"), + {"enable_content": get_variant(Str, enable_content)}, + {}, + locale, + ) + assert subman.is_registered + + +@pytest.mark.parametrize( + "credentials", + [("wrong username", None, None), (None, "wrong password", None), (None, None, "wrong organization")], +) +def test_register_with_wrong_values(external_candlepin, subman, test_config, credentials): + """ + https://www.candlepinproject.org/docs/subscription-manager/dbus_objects.html#methods-6 + """ + assert not subman.is_registered + + candlepin_config = partial(test_config.get, "candlepin") + + proxy = RHSM.get_proxy(RHSM_REGISTER_SERVER) + with RHSMPrivateBus(proxy) as private_bus: + private_proxy = private_bus.get_proxy(RHSM.service_name, RHSM_REGISTER.object_path) + + wrong_org = "wrong-organization" + + username = credentials[0] or candlepin_config("username") + password = credentials[1] or candlepin_config("password") + organization = credentials[2] or "" + + with pytest.raises(DBusError) as excinfo: + private_proxy.Register(organization, username, password, {}, {}, locale) + logger.debug(f"raised exception: {excinfo}") + if credentials.organization == wrong_org: + assert f"Organization {wrong_org} does not exist." in str(excinfo.value) + else: + assert "Invalid Credentials" in str(excinfo.value) + assert not subman.is_registered diff --git a/integration-tests/utils.py b/integration-tests/utils.py new file mode 100644 index 0000000000..ef4b54c9bd --- /dev/null +++ b/integration-tests/utils.py @@ -0,0 +1,21 @@ +import time + + +def loop_until(predicate, poll_sec=5, timeout_sec=120): + """ + An helper function to handle a time period waiting for an external service + to update its state. + + an example: + + assert loop_until(lambda: insights_client.is_registered) + + The loop function will retry to run predicate every 5secs + until the total time exceeds timeout_sec. + """ + start = time.time() + ok = False + while (not ok) and (time.time() - start < timeout_sec): + time.sleep(poll_sec) + ok = predicate() + return ok diff --git a/man/asciidoc/rhsm.conf.5.asciidoc b/man/asciidoc/rhsm.conf.5.asciidoc index 1877e71111..b2eb48dc78 100644 --- a/man/asciidoc/rhsm.conf.5.asciidoc +++ b/man/asciidoc/rhsm.conf.5.asciidoc @@ -36,26 +36,26 @@ insecure:: proxy_hostname:: Set this to a non-blank value if *subscription-manager* should use a - reverse proxy to access the subscription service. This sets the host - for the reverse proxy. Overrides hostname from *HTTP_PROXY* and + proxy to access the subscription service. This sets the host + for the proxy. Overrides hostname from *HTTP_PROXY* and *HTTPS_PROXY* environment variables. proxy_port:: Set this to a non-blank value if *subscription-manager* should use a - reverse proxy to access the subscription service. This sets the port - for the reverse proxy. Overrides port from *HTTP_PROXY* and + proxy to access the subscription service. This sets the port + for the proxy. Overrides port from *HTTP_PROXY* and *HTTPS_PROXY* environment variables. proxy_username:: Set this to a non-blank value if *subscription-manager* should use an - authenticated reverse proxy to access the subscription service. This - sets the username for the reverse proxy. Overrides username from + authenticated proxy to access the subscription service. This + sets the username for the proxy. Overrides username from *HTTP_PROXY* and *HTTPS_PROXY* environment variables. proxy_password:: Set this to a non-blank value if *subscription-manager* should use an - authenticated reverse proxy to access the subscription service. This - sets the username for the reverse proxy. Overrides password from + authenticated proxy to access the subscription service. This + sets the username for the proxy. Overrides password from *HTTP_PROXY* and *HTTPS_PROXY* environment variables. no_proxy:: @@ -129,11 +129,6 @@ pluginConfDir:: certCheckInterval:: The number of minutes between runs of the *rhsmcertd* daemon -autoAttachInterval:: - The number of minutes between attempts to run auto-attach on this - consumer. - - AUTHOR ------ Bryan Kearney diff --git a/man/rhsm.conf.5 b/man/rhsm.conf.5 index ae45269e28..3a8a65291f 100644 --- a/man/rhsm.conf.5 +++ b/man/rhsm.conf.5 @@ -66,7 +66,7 @@ proxy_hostname .RS 4 Set this to a non\-blank value if \fBsubscription\-manager\fR -should use a reverse proxy to access the subscription service\&. This sets the host for the reverse proxy\&. Overrides hostname from \fBHTTP_PROXY\fR and \fBHTTPS_PROXY\fR environment variables\&. This value +should use a proxy to access the subscription service\&. This sets the host for the proxy\&. Overrides hostname from \fBHTTP_PROXY\fR and \fBHTTPS_PROXY\fR environment variables\&. This value .B should not contain the scheme to be used with the proxy (e.g. http or https)\&. To specify that use the .B proxy_scheme @@ -75,17 +75,17 @@ option\&. .PP proxy_scheme .RS 4 -This only sets the scheme for the reverse proxy when writing out the proxy to repo definitions\&. Set this to a non\-blank value if you want to specify the scheme used by your package manager for subscription\-manager managed repos\&. This defaults to "http"\&. +This only sets the scheme for the proxy when writing out the proxy to repo definitions\&. Set this to a non\-blank value if you want to specify the scheme used by your package manager for subscription\-manager managed repos\&. This defaults to "http"\&. \fBNote:\fR -subscription-manager tooling does not use this option for connecting reverse proxy and HTTPS is always used. +subscription-manager tooling does not use this option for connecting proxy and HTTPS is always used. .RE .PP proxy_port .RS 4 Set this to a non\-blank value if \fBsubscription\-manager\fR -should use a reverse proxy to access the subscription service\&. This sets the port for the reverse proxy\&. Overrides port from \fBHTTP_PROXY\fR and \fBHTTPS_PROXY\fR environment variables\&. +should use a proxy to access the subscription service\&. This sets the port for the proxy\&. Overrides port from \fBHTTP_PROXY\fR and \fBHTTPS_PROXY\fR environment variables\&. Please note that setting this to any value other than 3128 (depending on your SELinux configuration) will require an update to that policy. @@ -102,14 +102,14 @@ proxy_username .RS 4 Set this to a non\-blank value if \fBsubscription\-manager\fR -should use an authenticated reverse proxy to access the subscription service\&. This sets the username for the reverse proxy\&. Overrides username from \fBHTTP_PROXY\fR and \fBHTTPS_PROXY\fR environment variables\&. +should use an authenticated proxy to access the subscription service\&. This sets the username for the proxy\&. Overrides username from \fBHTTP_PROXY\fR and \fBHTTPS_PROXY\fR environment variables\&. .RE .PP proxy_password .RS 4 Set this to a non\-blank value if \fBsubscription\-manager\fR -should use an authenticated reverse proxy to access the subscription service\&. This sets the password for the reverse proxy\&. Overrides password from \fBHTTP_PROXY\fR and \fBHTTPS_PROXY\fR environment variables\&. +should use an authenticated proxy to access the subscription service\&. This sets the password for the proxy\&. Overrides password from \fBHTTP_PROXY\fR and \fBHTTPS_PROXY\fR environment variables\&. .RE .PP no_proxy @@ -240,14 +240,9 @@ The number of minutes between runs of the daemon .RE .PP -autoAttachInterval -.RS 4 -The number of minutes between attempts to run auto\-attach on this consumer\&. -.RE -.PP splay .RS 4 -1 to enable splay. 0 to disable splay. If enabled, this feature delays the initial auto attach and cert check by an amount between 0 seconds and the interval given for the action being delayed. For example if the +1 to enable splay. 0 to disable splay. If enabled, this feature delays the initial cert check by an amount between 0 seconds and the interval given for the action being delayed. For example if the .B certCheckInterval were set to 3 minutes, the initial cert check would begin somewhere between 2 minutes after start up (minimum delay) and 5 minutes after start up. This is useful to reduce peak load on the Satellite or entitlement service used by a large number of machines. .RE diff --git a/man/rhsmcertd.8 b/man/rhsmcertd.8 index ea2143c268..4623ac7ffe 100644 --- a/man/rhsmcertd.8 +++ b/man/rhsmcertd.8 @@ -3,12 +3,12 @@ rhsmcertd \- Periodically scans and updates the entitlement certificates on a registered system. .SH SYNOPSIS -rhsmcertd [--cert-check-interval=MINUTES] [--auto-attach-interval=MINUTES] [--auto-registration-interval] [--no-splay] [--now] [--auto-registration] [--debug] [--help] +rhsmcertd [--cert-check-interval=MINUTES] [--auto-registration-interval] [--no-splay] [--now] [--auto-registration] [--debug] [--help] .PP .I Deprecated usage .PP -rhsmcertd [\fIcertInterval autoattachInterval\fP] +rhsmcertd [\fIcertInterval\fP] .SH DESCRIPTION Red Hat provides content updates and support by issuing @@ -21,7 +21,7 @@ When subscriptions are applied to a system or when new subscriptions are availab process runs periodically to check for changes in the subscriptions available to a machine by updating the entitlement certificates installed on the machine and by installing new entitlement certificates as they're available. .PP -At a defined interval, the process checks with the subscription management service to see if any new subscriptions are available to the system. If there are, it pulls in the associated subscription certificates. If any subscriptions have expired and new subscriptions are available, then the \fBrhsmcertd\fP process will automatically request those subscriptions. By default, the initial auto-attach is delayed by a random amount of seconds from zero to the \fBautoAttachInterval\fP. The initial cert check is delayed by a random amount of seconds from zero to \fBcertCheckInterval\fP. +At a defined interval, the process checks with the subscription management service to see if any new subscriptions are available to the system. If there are, it pulls in the associated subscription certificates. If any subscriptions have expired and new subscriptions are available, then the \fBrhsmcertd\fP process will automatically request those subscriptions. By default, the initial cert check is delayed by a random amount of seconds from zero to \fBcertCheckInterval\fP. .PP This \fbrhsmcertd\fP process can also perform automatic registration, when VM is running in the public cloud. Three public cloud providers are supported: AWS, Azure and GCP. When it is desired to perform automatic registration by rhsmcertd, then it is also necessary to configure mapping of "Cloud ID" to "RHSM organization ID" on https://cloud.redhat.com. @@ -33,7 +33,7 @@ rhsmcertd-worker.py script to perform the certificate add and update operations. .PP -Both the certificate interval and the auto-attach interval are configurable and can be reset through the \fBrhsmcertd\fP daemon itself or by editing the Subscription Manager \fB/etc/rhsm/rhsm.conf\fP file. +The certificate interval is configurable and can be reset through the \fBrhsmcertd\fP daemon itself or by editing the Subscription Manager \fB/etc/rhsm/rhsm.conf\fP file. .PP .B rhsmcertd @@ -58,12 +58,6 @@ Resets the interval for checking for new subscription certificates. This value i .B /etc/rhsm/rhsm.conf file are used (unless the argument is passed again). -.TP -.B -i, --auto-attach-interval=MINUTES -Resets the interval for checking for and replacing expired subscriptions. This value is in minutes. The default is 1440, or 24 hours. This interval is in effect until the daemon restarts, and then the values in the -.B /etc/rhsm/rhsm.conf -file are used (unless the argument is passed again). - .TP .B -r, --auto-registration-interval=MINUTES Resets the interval for automatic registration. This value is in minutes. The default is 60, or 1 hour. This interval is in effect until the daemon restarts, and then the values in the @@ -93,22 +87,22 @@ service rhsmcertd stop rhsmcertd --cert-check-interval=240 .fi -.SS RUNNING CERTIFICATE AND HEALING SCANS IMMEDIATELY -Normally, the certificate and auto-attach scans are run periodically, on a schedule defined in the \fBrhsmcertd\fP configuration. The scans can be run immediately -- which is useful if an administrator knows that there are new subscriptions available -- and then the scans resume their schedules. +.SS RUNNING CERTIFICATE SCANS IMMEDIATELY +Normally, the certificate scans are run periodically, on a schedule defined in the \fBrhsmcertd\fP configuration. The scans can be run immediately -- which is useful if an administrator knows that there are new subscriptions available -- and then the scans resume their schedules. .nf service rhsmcertd stop rhsmcertd -n .fi .SS DEPRECATED USAGE -\fBrhsmcertd\fP used to allow the certificate and auto-attach intervals to be reset simply by passing two integers as arguments. +\fBrhsmcertd\fP used to allow the certificate intervals to be reset simply by passing an integer argument. .PP -\fBrhsmcertd\fP \fIcertInterval autoAttachInterval\fP +\fBrhsmcertd\fP \fIcertInterval\fP .PP For example: .nf service rhsmcertd stop -rhsmcertd 180 480 +rhsmcertd 180 .fi .PP This usage is still allowed, but it is deprecated and not recommended. diff --git a/man/subscription-manager.8 b/man/subscription-manager.8 index 25d1ebf884..72779b0b97 100644 --- a/man/subscription-manager.8 +++ b/man/subscription-manager.8 @@ -26,12 +26,8 @@ is the command-line based client for the Red Hat Subscription Manager tool. Subscription Manager performs several key operations: .IP * It registers systems to the Red Hat subscription management service and adds the system to the inventory. Once a system is registered, it can receive updates based on its subscriptions to any kind of software products. -.IP -* It lists both available and used subscriptions. -.IP -* It allows administrators to both attach specific subscriptions to a system and remove those subscriptions. .PP -Subscription Manager can be used to auto-attach subscriptions to a system, as well. The +The .B subscription-manager command can even be invoked as part of a kickstart process. @@ -77,66 +73,52 @@ must be passed as system arguments in a non-interactive session. 2. unregister .IP -3. attach - -.IP -4. auto-attach - -.IP -5. remove +3. release .IP -6. release +4. list .IP -8. redeem +5. refresh .IP -9. list +6. environments .IP -10. refresh +7. repos .IP -11. environments +8. orgs .IP -12. repos +9. plugins .IP -13. orgs +10. identity .IP -14. plugins +11. facts .IP -15. identity +12. clean .IP -16. facts +13. config .IP -17. clean +14. version .IP -18. config +15. status .IP -19. version +16. syspurpose .IP -20. status - -.IP -21. syspurpose - -.IP -22. repo-override +17. repo-override .RE -Following commands were deprecated: subscribe and unsubscribe - .SS COMMON OPTIONS .TP .B -h, --help @@ -190,10 +172,6 @@ Gives the username for the account which is registering the system; this user ac .B --password=PASSWORD Gives the user account password. -.TP -.B --token=TOKEN -Token to use when authorizing against the server. - .TP .B --serverurl=SERVER_HOSTNAME Passes the name of the subscription service with which to register the system. The default value, if this is not given, is the Customer Portal Subscription Management service, @@ -237,11 +215,11 @@ References an existing system inventory ID to resume using a previous registrati .TP .B --activationkey=KEYS -Gives a comma-separated list of product keys to use to redeem or apply specific subscriptions to the system. This is used for preconfigured systems which may already have products installed. Activation keys are issued by an on-premise subscription management service, such as Subscription Asset Manager. +Gives a comma-separated list of product keys to apply specific subscriptions to the system. This is used for preconfigured systems which may already have products installed. Activation keys are issued by an on-premise subscription management service, such as Subscription Asset Manager. .IP When the .B --activationkey -option is used, it is not necessary to use the +option is used, it is not possible to use the .B --username and .B --password @@ -254,15 +232,6 @@ subscription-manager register --org="IT Dept" --activationkey=1234abcd .fi .RE -.TP -.B --auto-attach -Automatically attaches compatible subscriptions to this system. - - -.TP -.B --servicelevel=LEVEL -Sets the preferred service level to use with subscriptions added to the system. Service levels are commonly premium, standard, and none, though other levels may be available depending on the product and the contract. - .TP .B --force When the system is already registered, a new attempt to register will fail with a message reminding the user that the system is already registered. However, passing the @@ -297,87 +266,6 @@ command does two important things. Firstly, it will implicitly remove all of the .PP This command has no options. -.SS ATTACH OPTIONS -The -.B attach -command applies a specific subscription to the system. This command is not possible to use, when the content access mode of the organization to which the system is registered is simple content access mode. - -.TP -.B --auto -Automatically attaches the best-matched compatible subscription or subscriptions to the system. This is the default unless -.B --pool -or -.B --file -are used. - -.TP -.B --pool=POOLID -Gives the ID for the subscriptions pool (collection of products) to attach to the system. This overrides the default of --auto. - -.TP -.B --file=FILE -Specifies a file from which to read whitespace-delimited pool IDs. If FILE is "-", the pool IDs will be read from stdin. This overrides the default of -.B ---auto. - -.TP -.B --quantity=NUMBER -Attaches a specified number of subscriptions to the system. Subscriptions may have certain limits on them, like the number of sockets on the system or the number of allowed virtual guests. It is possible to attach multiple subscriptions (or -.I stacking -subscriptions) to cover the number of sockets, guests, or other characteristics. May not be used with an auto-attach. - - -.TP -.B --servicelevel=LEVEL -Sets the preferred service level to use with subscriptions automatically attached to the system. Service levels are commonly premium, standard, and none, though other levels may be available depending on the product and the contract. This option cannot be used when attaching specific pools via -.B --pool -or -.B --file. - -.SS AUTO-ATTACH OPTIONS -The -.B auto-attach -command sets whether the ability to check, attach, and update subscriptions occurs automatically on the system. Auto-attaching subscriptions checks the currently-installed products, attached subscriptions, and any changes in available subscriptions every four hours using the \fBrhsmcertd\fP daemon. - -.TP -.B --enable -Enables the auto-attach option for the system. If there is any change in the subscriptions for the system, any subscriptions expire, or any new products are installed, then \fBsubscription-manager\fP detects the changes and automatically attaches the appropriate subscriptions so that the system remains covered. - -.TP -.B --disable -Disables the auto-attach option for the system. If auto-attach is disabled, then any changes in installed products or subscriptions for the system (including expired subscriptions) must be addressed manually by the administrator. - -.TP -.B --show -Shows whether auto-attach is enabled on the systems. - -.SS REMOVE OPTIONS -The -.B remove -command removes a subscription from the system. (This does not uninstall the associated products.) - -.TP -.B --serial=SERIALNUMBER -Gives the serial number of the subscription certificate for the specific product to remove from the system. Subscription certificates attached to a system are in a certificate, in -.B /etc/pki/entitlement/.pem. -To remove multiple subscriptions, use the -.B --serial -option multiple times. - -.TP -.B --pool=POOLID -Removes all subscription certificates for the specified pool id from the system. -To remove multiple sets of subscriptions, use the -.B --pool -option multiple times. - -.TP -.B --all -Removes -.I all -of the subscriptions attached to a system. - - .SS RELEASE OPTIONS The .B release @@ -408,9 +296,6 @@ The .B syspurpose command has subcommands for all the various syspurpose preferences and attributes: -.IP -1. addons - .IP 2. role @@ -427,56 +312,13 @@ Shows the system's current set of syspurpose preference formatted as JSON. Singl .PP -.SS addons options -The -.B addons -subcommand displays the current configured addons system purpose attribute -.I preference -for products installed on the system. For example, if the addons preference is ADDON1, then a subscription with a ADDON1 addon is selected when auto-attaching subscriptions to the system. - -.TP -.B --show -Shows the system's current addons preference. If a addons is not set, then there is a message saying it is not set. - -.TP -.B --list -Lists the available addons system purpose values. - -.TP -.B --username=USERNAME -Gives the username for the account to use to connect to the organization account [Usable with --list on unregistered systems]. - -.TP -.B --password=PASSWORD -Gives the user account password [Usable with --list on unregistered systems]. - -.TP -.B --token=TOKEN -Token to use when authorizing against the server [Usable with --list on unregistered systems]. - -.TP -.B --org=ORG -Identifies the organization for which the addons apply [Usable with --list on unregistered systems]. - -.TP -.B --add=ADDON -Addon to add to the list of requested addons for this system - -.TP -.B --remove=ADDON -Remove the addon from the list of requested addons. - -.TP -.B --unset -Removes all addons from the list of requested addons. - .SS role options The .B role subcommand displays the current configured role .I preference -for products installed on the system. For example, if the role preference is "Red Hat Enterprise Linux Server", then a subscription with a "Red Hat Enterprise Linux Server" role is selected when auto-attaching subscriptions to the system. +for products installed on the system. For example, if the role preference is "Red Hat Enterprise Linux Server", then a subscription with a "Red Hat Enterprise Linux Server" role is selected when attaching subscriptions to the system. .TP .B --show @@ -494,10 +336,6 @@ Gives the username for the account to use to connect to the organization account .B --password=PASSWORD Gives the user account password [Usable with --list on unregistered systems]. -.TP -.B --token=TOKEN -Token to use when authorizing against the server [Usable with --list on unregistered systems]. - .TP .B --org=ORG Identifies the organization for which the role applies [Usable with --list on unregistered systems]. @@ -516,7 +354,7 @@ The .B service-level subcommand displays the current configured service level .I preference -for products installed on the system. For example, if the service-level preference is standard, then a subscription with a standard service level is selected when auto-attaching subscriptions to the system. +for products installed on the system. For example, if the service-level preference is standard, then a subscription with a standard service level is selected when attaching subscriptions to the system. .TP .B --serverurl=SERVER_URL @@ -542,10 +380,6 @@ Gives the username for the account to use to connect to the organization account .B --password=PASSWORD Gives the user account password [Usable with --list on unregistered systems]. -.TP -.B --token=TOKEN -Token to use when authorizing against the server [Usable with --list on unregistered systems]. - .TP .B --set=SERVICE_LEVEL Service level to apply to this system @@ -560,7 +394,7 @@ The .B usage subcommand displays the current configured usage .I preference -for products installed on the system. For example, if the usage preference is "Production", then a subscription with a "Production" usage is selected when auto-attaching subscriptions to the system. +for products installed on the system. For example, if the usage preference is "Production", then a subscription with a "Production" usage is selected when attaching subscriptions to the system. .TP .B --show @@ -578,10 +412,6 @@ Gives the username for the account to use to connect to the organization account .B --password=PASSWORD Gives the user account password [Usable with --list on unregistered systems]. -.TP -.B --token=TOKEN -Token to use when authorizing against the server [Usable with --list on unregistered systems]. - .TP .B --org=ORG Identifies the organization for which the usage applies [Usable with --list on unregistered systems]. @@ -594,79 +424,22 @@ Usage to apply to this system .B --unset Removes any previously set usage preference. - -.SS REDEEM OPTIONS -The -.B redeem -command is used for systems that are purchased from third-party vendors that include a subscription. The redemption process essentially auto-attaches the preselected subscription that the vendor supplied to the system. - -.TP -.B --email=EMAIL -Gives the email account to send the redemption notification message to. - -.TP -.B --locale=LOCALE -Sets the locale to use for the message. If none is given, then it defaults to the local system's locale. - - .SS LIST OPTIONS The .B list -command lists all of the subscriptions that are compatible with a system. The options allow the list to be filtered by subscriptions that are used by the system or unused subscriptions that are available to the system. - -.TP -.B --afterdate=YYYY-MM-DD -Shows pools that are active on or after the given date. This is only used with the -.B --available -option. - -.TP -.B --all -Lists all possible subscriptions that have been purchased, even if they don't match the architecture of the system. This is used with the -.B --available -option. - -.TP -.B --available -Lists available subscriptions which are not yet attached to the system. - -.TP -.B --consumed -Lists all of the subscriptions currently attached to the system. +command lists all of the installed products on the system. The options allow the list to be filtered. .TP .B --installed -Lists products which are currently installed on the system which may (or may not) have subscriptions associated with them, as well as products with attached subscriptions which may (or may not) be installed. (default) - -.TP -.B --ondate=YYYY-MM-DD -Sets the date to use to search for active and available subscriptions. The default (if not explicitly passed) is today's date; using a later date looks for subscriptions which will be active then. This is only used with the -.B --available -option. - -.TP -.B --no-overlap -Shows pools which provide products that are not already covered; only used with -.B --available -option. - -.TP -.B --match-installed -Shows only subscriptions matching products that are currently installed; only used with -.B --available -option. +Lists products which are currently installed on the system. (default) .TP .B --matches=SEARCH -Limits the output of --installed, --available and --consumed to only subscriptions or products which contain SEARCH in the subscription or product information, varying with the list requested and the server version. +Limits the output of installed products which contain SEARCH in product information. .br SEARCH may contain the wildcards ? or * to match a single character or zero or more characters, respectively. The wildcard characters may be escaped with a backslash to represent a literal question mark or asterisk. Likewise, to represent a backslash, it must be escaped with another backslash. -.TP -.B --pool-only -Limits the output of --available and --consumed such that only the pool IDs are displayed. No labels or errors will be printed if this option is specified. - .SS REFRESH OPTIONS The .B refresh @@ -694,10 +467,6 @@ Gives the username for the account to use to connect to the organization account .B --password=PASSWORD Gives the user account password. -.TP -.B --token=TOKEN -Token to use when authorizing against the server. - .TP .B --org=ORG Identifies the organization for which to list the configured environments. @@ -762,10 +531,6 @@ Gives the username for the account to use to connect to the organization account .B --password=PASSWORD Gives the user account password. -.TP -.B --token=TOKEN -Token to use when authorizing against the server. - .TP .B --serverurl=SERVER_HOSTNAME Passes the name of the subscription service to use to list all available organizations. The \fBorgs\fP command will list all organizations for the specified service for which the user account is granted access. The default value, if this is not given, is the Customer Portal Subscription Management service, @@ -849,19 +614,15 @@ Gives the username for the account which is registering the system; this user ac .B --password=PASSWORD Gives the user account password. Optional, for user-based authentication. -.TP -.B --token=TOKEN -Token to use when authorizing against the server. - .TP .B --force -Regenerates the identity certificate for the system using username/password or token authentication. This is used with the +Regenerates the identity certificate for the system using username/password authentication. This is used with the .B --regenerate option. .B --regenerate alone will use an existing identity certificate to authenticate to the subscription management service. If the certificate is missing or corrupted or in other circumstances, then it may be better to use user authentication rather than certificate-based authentication. In that case, the .B --force -option requires the username or password or token to be given either as an argument or in response to a prompt. +option requires the username or password to be given either as an argument or in response to a prompt. .SS FACTS OPTIONS @@ -1013,15 +774,7 @@ Overall Status: Insufficient .RE .SS DEPRECATED COMMANDS -As the structures of subscription configuration have changed, some of the original management commands have become obsolete. These commands have been replaced with updated commands. - -.TP -.B subscribe -This has been replaced with attach. A similar registration option, \fB--subscribe\fP, has also be replaced with \fB--auto-attach\fP. - -.TP -.B unsubscribe -This has been replaced with \fBremove\fP. +No commands are currently deprecated. .SH USAGE .B subscription-manager @@ -1054,16 +807,16 @@ If a system has never been registered (not even during first boot), then the .B register command will register the system with whatever subscription management service is configured in the .B /etc/rhsm/rhsm.conf -file. This command requires, at a minimum, the username and password or token for an account to connect to the subscription management service. If the credentials aren't passed with the command, then +file. This command requires, at a minimum, the username and password for an account to connect to the subscription management service. If the credentials aren't passed with the command, then .B subscription-manager prompts for the username and password interactively. .PP -When there is a single organization or when using the Customer Portal Subscription Management service, all that is required is the username/password set or the token is used. For example: +When there is a single organization or when using the Customer Portal Subscription Management service, all that is required is the username/password set. For example: .RS .nf -subscription-manager register --username=admin --password=secret or subscription-manager register --token=eyJhbGciOiJSUzI1NiIsI ... stGc_2bFDQC8CENEOo +subscription-manager register --username=admin --password=secret .fi .RE @@ -1134,40 +887,11 @@ subscription-manager unregister .fi .RE -.PP -An option with registration, -.B --auto-attach, -will automatically attach the subscriptions pool which best matches the system architecture and configuration to the newly-registered system. This option attaches subscriptions as part of the registration process, rather than separately managing subscriptions. - -.RS -.nf -subscription-manager register --username=admin --password=secret ---auto-attach -.fi -.RE - -.PP -Auto-attach also supports an option to set a preferred service level with the selected subscriptions, the -.B --servicelevel -option. In this case, the -.B --servicelevel -option sets a preference that helps the auto-attach process select appropriate subscriptions. For example, if the preferred service level for a production server is premium, and there are three matching subscriptions with different service levels (none, standard, and premium), the auto-attach process selects the subscription which offers a premium service level. - -.RS -.nf -subscription-manager register --username=admin --password=secret ---auto-attach --servicelevel=premium -.fi -.RE - -.SS LISTING, ATTACHING, AND REMOVING SUBSCRIPTIONS FOR PRODUCTS +.SS LISTING SUBSCRIPTIONS FOR PRODUCTS A .I subscription is essentially the right to install, use, and receive updates for a Red Hat product. (Sometimes multiple individual software products are bundled together into a single subscription.) When a system is registered, the subscription management service is aware of the system and has a list of all of the possible product subscriptions that the system can install and use. A subscription is applied to a system when the system is -.I attached -to the subscription pool that makes that product available. A system releases or -.I removes -that subscription (meaning, it removes that subscription so that another system can use that subscription count). +attached to the subscription pool that makes that product available. .PP The .B list @@ -1242,91 +966,6 @@ Ends: 08/31/2015 .fi .RE -.PP -Attaching a subscription requires the ID for the subscription pool (the -.I --pool -option). For example: - -.RS -.nf -subscription-manager attach ---pool=ff8080812bc382e3012bc3845da100d2 -.fi -.RE - -.pp -As with the -.B register -command, the system can be auto-attached to the best-fitting subscriptions. This is the default action and is equivalent to using the -.B --auto -option: - -.RS -.nf -subscription-manager attach -.fi -.RE - - -.PP -Auto-attach also supports an option to set a preferred service level with the selected subscriptions, the -.B --servicelevel -option. In this case, the -.B --servicelevel -option sets a preference that helps the auto-attach process select appropriate subscriptions. For example, if the preferred service level for a production server is premium, and there are three matching subscriptions with different service levels (none, standard, and premium), the auto-attach process selects the subscription which offers a premium subscription. - -.RS -.nf -subscription-manager attach --servicelevel=premium -.fi -.RE - -.PP -Some subscriptions define a count based on attributes of the system itself, like the number of sockets or the number of virtual guests on a host. You can combine multiple subscriptions together to cover the count. For example, if there is a four socket server, you can use two subscriptions for "RHEL Server for Two Sockets" to cover the socket count. To specify the number of subscriptions to use, -use the -.B --quantity -option. For example: - -.RS -.nf -subscription-manager attach ---pool=ff8080812bc382e3012bc3845da100d2 ---quantity=2 -.fi -.RE - -.PP -Removing subscription from a system releases the subscription back into the pool. The system remains registered with the subscription management service. Each product has an identifying X.509 certificate installed with it. To remove a subscription for a specific product, specify the serial number (or numbers, in multiple \fB--serial\fP options) of the certificate: - -.RS -.nf -subscription-manager remove --serial=1128750306742160 -.fi -.RE - -.PP -Giving the -.B remove -command with the -.B --all -option removes every subscription the system has used. - - -.SS REDEEMING EXISTING SUBSCRIPTIONS -Sometimes, a system may come preconfigured with products and subscriptions. Rather than attaching a pool and claiming a subscription, this system simply needs to -.I redeem -its existing subscriptions. - -.PP -After registration, subscriptions on preconfigured systems can be claimed using the -.B redeem -command, which essentially auto-attaches the system to its preexisting subscriptions. - -.RS -.nf -subscription-manager redeem --email=admin@example.com --org="IT Dept" -.fi -.RE .SS VIEWING LOCAL SUBSCRIPTION & CONTENT PROVIDER INFORMATION Red Hat has a hosted environment, through the Customer Portal, that provides centralized access to subscription management and content repositories. However, organizations can use other tools -- like Subscription Manager -- for content hosting and subscription management. With a local content provider, the organization, environments, repositories, and other structural configuration is performed in the content provider. Red Hat Subscription Manager can be used to display this information, using the @@ -1342,24 +981,15 @@ subscription-manager repos --list subscription-manager environments --username=jsmith --password=secret --org=prod - or - - subscription-manager environments --token=eyJhbGciOiJSUzI1NiIsI ... stGc_2bFDQC8CENEOo --org=prod - - subscription-manager orgs --username=jsmith --password=secret - -or - -subscription-manager orgs --token=eyJhbGciOiJSUzI1NiIsI ... stGc_2bFDQC8CENEOo .fi .RE .SS CHANGING SUBSCRIPTION MANAGER CONFIGURATION The Subscription Manager CLI and GUI both use the .B /etc/rhsm/rhsm.conf -file for configuration, including what content and subscription management services to use and management settings like auto-attaching. This configuration file can be edited directly, or it can be edited using the +file for configuration, including what content and subscription management services to use. This configuration file can be edited directly, or it can be edited using the .B config command. Parameters and values are passed as arguments with the .B config @@ -1454,7 +1084,7 @@ tool can be run as a post-install script as part of the kickstart installation p .RS .nf %post --log=/root/ks-post.log -/usr/sbin/subscription-manager register --username admin --password secret --org 'east colo' --auto-attach --servicelevel=premium --force +/usr/sbin/subscription-manager register --username admin --password secret --org 'east colo' --force .fi .RE diff --git a/po/de.po b/po/de.po index 87441fd0a6..feaabb98ec 100644 --- a/po/de.po +++ b/po/de.po @@ -5,15 +5,15 @@ # ljanda , 2019. #zanata # CoconutNut , 2021. # Ludek Janda , 2021. -# Ettore Atalan , 2021, 2022. +# Ettore Atalan , 2021, 2022, 2024. # Pino Toscano , 2023. msgid "" msgstr "" "Project-Id-Version: rhsm\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-07-21 12:27+0200\n" -"PO-Revision-Date: 2023-01-20 19:20+0000\n" -"Last-Translator: Pino Toscano \n" +"PO-Revision-Date: 2024-11-09 18:38+0000\n" +"Last-Translator: Ettore Atalan \n" "Language-Team: German \n" "Language: de\n" @@ -21,7 +21,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.15.1\n" +"X-Generator: Weblate 5.8.2\n" #: /usr/lib64/python3.9/argparse.py:296 msgid "usage: " @@ -113,7 +113,7 @@ msgstr "Unerkannte Argumente: %s" #: /usr/lib64/python3.9/argparse.py:1928 #, python-format msgid "not allowed with argument %s" -msgstr "nicht erlaubt mit dem Aurgument: %s" +msgstr "nicht erlaubt mit Argument %s" #: /usr/lib64/python3.9/argparse.py:1974 /usr/lib64/python3.9/argparse.py:1988 #, python-format @@ -129,7 +129,7 @@ msgstr "die folgenden Argumente sind erforderlich: %s" #: /usr/lib64/python3.9/argparse.py:2110 #, python-format msgid "one of the arguments %s is required" -msgstr "eines der Argumente %s ist benötigt" +msgstr "eines der Argumente %s ist erforderlich" #: /usr/lib64/python3.9/argparse.py:2153 msgid "expected one argument" diff --git a/po/fr.po b/po/fr.po index 2641df4fd6..ba3278e8b4 100644 --- a/po/fr.po +++ b/po/fr.po @@ -12,13 +12,14 @@ # Transtats , 2022, 2023. # blutch112 , 2022. # Pino Toscano , 2023. +# Léane GRASSER , 2024. msgid "" msgstr "" "Project-Id-Version: rhsm\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-07-21 12:27+0200\n" -"PO-Revision-Date: 2023-09-11 09:17+0000\n" -"Last-Translator: Pino Toscano \n" +"PO-Revision-Date: 2024-12-13 12:38+0000\n" +"Last-Translator: Léane GRASSER \n" "Language-Team: French \n" "Language: fr\n" @@ -26,7 +27,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 4.18.2\n" +"X-Generator: Weblate 5.8.4\n" #: /usr/lib64/python3.9/argparse.py:296 msgid "usage: " @@ -39,7 +40,7 @@ msgstr ".__call__() non défini" #: /usr/lib64/python3.9/argparse.py:1204 #, python-format msgid "unknown parser %(parser_name)r (choices: %(choices)s)" -msgstr "analyseur syntaxique inconnu %(parser_name)r (choix : %(choices)s)" +msgstr "analyseur syntaxique %(parser_name)r inconnu (choix : %(choices)s)" #: /usr/lib64/python3.9/argparse.py:1264 #, python-format @@ -54,7 +55,7 @@ msgstr "impossible d'ouvrir '%(filename)s' : %(error)s" #: /usr/lib64/python3.9/argparse.py:1482 #, python-format msgid "cannot merge actions - two groups are named %r" -msgstr "ne peut pas fusionner les actions - deux groupes sont nommés %r" +msgstr "impossible de fusionner les actions : deux groupes portent le nom %r" #: /usr/lib64/python3.9/argparse.py:1520 msgid "'required' is an invalid argument for positionals" @@ -109,7 +110,7 @@ msgstr "afficher ce message d'aide et quitter" # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: /usr/lib64/python3.9/argparse.py:1775 msgid "cannot have multiple subparser arguments" -msgstr "ne peut pas avoir plusieurs arguments de sous-analyse" +msgstr "impossible d'avoir plusieurs arguments de sous-analyseur" #: /usr/lib64/python3.9/argparse.py:1827 /usr/lib64/python3.9/argparse.py:2338 #, python-format @@ -139,15 +140,15 @@ msgstr "l'un des arguments %s est requis" #: /usr/lib64/python3.9/argparse.py:2153 msgid "expected one argument" -msgstr "attendait un argument" +msgstr "un argument attendu" #: /usr/lib64/python3.9/argparse.py:2154 msgid "expected at most one argument" -msgstr "attend au plus un argument" +msgstr "au plus un argument attendu" #: /usr/lib64/python3.9/argparse.py:2155 msgid "expected at least one argument" -msgstr "attendait au moins un argument" +msgstr "au moins un argument attendu" #: /usr/lib64/python3.9/argparse.py:2159 #, python-format @@ -301,8 +302,8 @@ msgid "" "subscription-manager to register.\n" msgstr "" "\n" -"Le système n'est pas inscrit auprès un serveur de droits d'accès. Utilisez " -"subscription-manager pour l'inscrire.\n" +"Le système n'est pas enregistré auprès un serveur de droits. Utilisez " +"subscription-manager pour l'enregistrer.\n" #: src/plugins/dnf/subscription_manager.py:52 msgid "" @@ -311,9 +312,9 @@ msgid "" "updates. You can use subscription-manager to assign subscriptions.\n" msgstr "" "\n" -"Ce système est inscrit auprès d'un serveur de droits d'accès, mais ne reçoit " -"pas de mises à jour. Vous pouvez utiliser le subscription-manager pour " -"assigner des abonnements.\n" +"Ce système est enregistré auprès d'un serveur de droits, mais ne reçoit pas " +"de mises à jour. Vous pouvez utiliser subscription-manager pour assigner des " +"abonnements.\n" #: src/plugins/dnf/subscription_manager.py:87 msgid "Not root, Subscription Management repositories not updated" @@ -984,11 +985,11 @@ msgstr "Vérification du statut de conformité" #: src/rhsm/connection.py:1782 msgid "Checking system purpose compliance status" -msgstr "Vérification de l'état de conformité de l'objet du système" +msgstr "Vérification de l'état de conformité de la finalité du système" #: src/rhsm/connection.py:1789 msgid "Fetching available system purpose settings" -msgstr "Extraire les paramètres disponibles de l'objectif du système" +msgstr "Récupération des paramètres de finalité du système disponibles" # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: src/rhsm/connection.py:1796 src/rhsm/connection.py:1804 @@ -1093,8 +1094,8 @@ msgid "" "This system is not yet registered. Try 'subscription-manager register --" "help' for more information." msgstr "" -"Ce système n'est pas encore inscrit. Essayez 'subscription-manager register " -"--help' pour obtenir plus d'informations." +"Ce système n'est pas encore enregistré. Essayez 'subscription-manager " +"register --help' pour obtenir plus d'informations." #: src/rhsm_debug/debug_commands.py:53 msgid "Assemble system information as a tar file or directory" @@ -1310,11 +1311,12 @@ msgstr "" #: src/rhsmlib/services/entitlement.py:519 msgid "Error: this system is not registered" -msgstr "Erreur : ce système n'est pas inscrit" +msgstr "Erreur : ce système n'est pas enregistré" #: src/rhsmlib/services/register.py:192 msgid "This system is already registered. Add force to options to override." -msgstr "Ce système est déjà inscrit. Ajouter force aux options à substituer." +msgstr "" +"Ce système est déjà enregistré. Ajoutez force aux options pour passer outre." # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/rhsmlib/services/register.py:195 @@ -1329,10 +1331,10 @@ msgid "" "with consumerid. Please use --force without --consumerid to re-register or " "use the clean command and try again without --force." msgstr "" -"Erreur : impossible de forcer l'inscription tout en tentant de récupérer " -"l'inscription précédente avec consumerid. Veuillez utiliser --force without " -"--consumerid pour réinscrire, ou utilisez la commande clean et tentez à " -"nouveau avec --force." +"Erreur : Impossible de forcer l'enregistrement tout en essayant de récupérer " +"l'enregistrement précédent avec consumerid. Veuillez utiliser --force sans --" +"consumerid pour effectuer un nouvel enregistrement, ou utilisez la commande " +"clean et essayez à nouveau sans --force." # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/rhsmlib/services/register.py:209 @@ -1353,8 +1355,8 @@ msgstr "" #: src/subscription_manager/cli_command/register.py:147 msgid "Error: Activation keys can not be used with previously registered IDs." msgstr "" -"Erreur : les clés d'activation ne peuvent pas être utilisées avec des ID " -"précédemment inscrits." +"Erreur : Les clés d'activation ne peuvent pas être utilisées avec des ID " +"précédemment enregistrés." # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/rhsmlib/services/register.py:218 @@ -1394,17 +1396,17 @@ msgstr "Désactivé" # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/branding/__init__.py:77 msgid "Register the system to the server" -msgstr "Inscrire le système sur le serveur" +msgstr "Enregistrer le système auprès du serveur" # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/branding/__init__.py:78 msgid "Unregister the system from the server" -msgstr "Désinscrire le système du serveur" +msgstr "Désenregistrer le système auprès du serveur" # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: src/subscription_manager/branding/__init__.py:79 msgid "This system is registered to spacewalk" -msgstr "Ce système est inscrit sur spacewalk" +msgstr "Ce système est enregistré auprès de spacewalk" # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/branding/__init__.py:81 @@ -1416,7 +1418,7 @@ msgstr "AVERTISSEMENT" # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/branding/__init__.py:81 msgid "You have already registered with spacewalk." -msgstr "Vous êtes déjà inscrit avec spacewalk." +msgstr "Vous êtes déjà enregistré auprès de spacewalk." # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: src/subscription_manager/branding/__init__.py:84 @@ -1444,8 +1446,8 @@ msgid "" "Register this system to the Customer Portal or another subscription " "management service" msgstr "" -"Inscrire ce système sur le Portail Client ou sur un autre service de gestion " -"des abonnements" +"Enregistrer ce système auprès du Customer Portal ou d'un autre service de " +"gestion d'abonnements" # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/branding/redhat_branding.py:10 @@ -1453,18 +1455,19 @@ msgid "" "Unregister this system from the Customer Portal or another subscription " "management service" msgstr "" -"Désinscrire ce système du Portail Client ou d'un autre service de gestion " -"des abonnements" +"Désenregistrer ce système auprès du Customer Portal ou d'un autre service de " +"gestion d'abonnements" # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/branding/redhat_branding.py:12 msgid "This system is registered to RHN Classic." -msgstr "Ce système est inscrit sur RHN Classic." +msgstr "Ce système est enregistré auprès de RHN Classic." # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/branding/redhat_branding.py:16 msgid "This system has already been registered with Red Hat using RHN Classic." -msgstr "Ce système a déjà été inscrit sur Red Hat à l'aide de RHN Classic." +msgstr "" +"Ce système a déjà été enregistré auprès de Red Hat à l'aide de RHN Classic." # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: src/subscription_manager/branding/redhat_branding.py:19 @@ -1472,9 +1475,9 @@ msgid "" "Your system is being registered again using Red Hat Subscription Management. " "Red Hat recommends that customers only register once." msgstr "" -"Votre système est en train d'être réinscrit à l'aide de Red Hat Subscription " -"Management. Red Hat recommande à ses clients de ne s'inscrire qu'une seule " -"fois." +"Votre système est en train d'être enregistré à nouveau à l'aide de Red Hat " +"Subscription Management. Red Hat recommande à ses clients de n'effectuer " +"l'enregistrement qu'une seule fois." # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/branding/redhat_branding.py:24 @@ -1482,8 +1485,8 @@ msgid "" "To learn how to unregister from either service please consult this Knowledge " "Base Article: https://access.redhat.com/kb/docs/DOC-45563" msgstr "" -"Pour apprendre comment se désinscrire de l'un des services, veuillez " -"consulter cet article de la base des connaissances : https://access.redhat." +"Pour savoir comment supprimer l'enregistrement auprès de l'un des services, " +"consultez cet article de la base de connaissances : https://access.redhat." "com/kb/docs/DOC-45563" # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz @@ -1502,14 +1505,14 @@ msgid "" "This system is registered using both RHN Classic and Red Hat Subscription " "Management." msgstr "" -"Ce système est inscrit avec les technologies RHN Classic et Red Hat " +"Ce système est enregistré avec les technologies RHN Classic et Red Hat " "Subscription Management." # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: src/subscription_manager/branding/redhat_branding.py:34 msgid "Red Hat recommends that customers only register with one service." msgstr "" -"Red Hat recommande à ses clients de n'inscrire le système qu'à un seul " +"Red Hat recommande à ses clients d'enregistrer le système auprès d'un seul " "service." # translation auto-copied from project subscription-manager, version 1.9.X, document keys @@ -1608,12 +1611,12 @@ msgstr "Obsolète, voir 'syspurpose'" #: src/subscription_manager/cli_command/abstract_syspurpose.py:75 #, python-brace-format msgid "set {attr} of system purpose" -msgstr "définir {attr} dans l'objet du système" +msgstr "définir {attr} dans la finalité du système" #: src/subscription_manager/cli_command/abstract_syspurpose.py:82 #, python-brace-format msgid "unset {attr} of system purpose" -msgstr "supprimer {attr} de l'objet du système" +msgstr "supprimer {attr} de la finalité du système" #: src/subscription_manager/cli_command/abstract_syspurpose.py:90 #, python-brace-format @@ -1652,16 +1655,16 @@ msgstr "Erreur : vous pouvez spécifier --username ou --token mais pas les deux" msgid "" "Error: you must register or specify --username and --password to list {attr}" msgstr "" -"Erreur : vous devez vous inscrire ou spécifier --username et --password pour " -"répertorier {attr}" +"Erreur : vous devez effectuer l'enregistrement ou spécifier --username et --" +"password pour lister {attr}" #: src/subscription_manager/cli_command/abstract_syspurpose.py:173 msgid "" "Error: --username, --password, --token and --org can be used only on " "unregistered systems" msgstr "" -"Erreur : --username, --password, --token et --org ne peuvent être utilisés " -"que sur des systèmes non inscrits" +"Erreur : --username, --password, --token et --org ne peuvent être utilisés " +"que sur des systèmes non enregistrés" #. TRANSLATORS: this is used to quote a string #: src/subscription_manager/cli_command/abstract_syspurpose.py:251 @@ -1690,9 +1693,9 @@ msgid "" "system purpose \"{attr}\". This setting will not influence auto-attaching " "subscriptions." msgstr "" -"Avertissement : Cette organisation n'a pas d'abonnements qui fournissent un " -"objet de système « {attr} ». Ce paramètre n'aura aucune incidence sur " -"l'auto-assignation des abonnements." +"Avertissement : Cette organisation n'a pas d'abonnements qui fournissent une " +"finalité du système « {attr} ». Ce paramètre n'aura aucune incidence sur l" +"'auto-assignation des abonnements." #: src/subscription_manager/cli_command/abstract_syspurpose.py:278 #, python-brace-format @@ -1732,8 +1735,8 @@ msgid "" "There are no available values for the system purpose \"{syspurpose_attr}\" " "from the available subscriptions in this organization." msgstr "" -"Il n'y a pas de valeurs disponibles pour l'objet du système " -"\"{syspurpose_attr}\" parmi les abonnements disponibles dans cette " +"Il n'y a pas de valeurs disponibles pour la finalité du système \"" +"{syspurpose_attr}\" parmi les abonnements disponibles dans cette " "organisation." #: src/subscription_manager/cli_command/abstract_syspurpose.py:409 @@ -1742,8 +1745,8 @@ msgid "" "Unable to get the list of valid values for the system purpose " "\"{syspurpose_attr}\"." msgstr "" -"Impossible d'obtenir la liste des valeurs valides pour l'objet du système " -"\"{syspurpose_attr}\"." +"Impossible d'obtenir la liste des valeurs valides pour la finalité du " +"système \"{syspurpose_attr}\"." #: src/subscription_manager/cli_command/abstract_syspurpose.py:435 msgid "Unable to connect to server using token" @@ -1763,11 +1766,11 @@ msgid "" "Purpose {attr}." msgstr "" "Remarque : le serveur de droits d'accès actuellement configuré ne prend pas " -"en charge l'objet du système {attr}." +"en charge la finalité du système {attr}." #: src/subscription_manager/cli_command/addons.py:24 msgid "Show or modify the system purpose addons setting" -msgstr "Afficher ou modifier les paramètres de l'extension objet du système" +msgstr "Afficher ou modifier les paramètres de l'extension finalité du système" #: src/subscription_manager/cli_command/attach.py:51 msgid "The ID of the pool to attach (can be specified more than once)" @@ -1784,7 +1787,7 @@ msgid "" "Automatically attach the best-matched compatible subscriptions to this " "system. This is the default action." msgstr "" -"Assigne automatiquement les abonnements compatibles les mieux adaptés à ce " +"Assigner automatiquement les abonnements compatibles les mieux adaptés à ce " "système. Il s'agit de l'action par défaut." #: src/subscription_manager/cli_command/attach.py:71 @@ -1820,8 +1823,8 @@ msgid "" "Attach a specified subscription to the registered system, when system does " "not use Simple Content Access mode" msgstr "" -"Attachez un abonnement spécifié au système enregistré, lorsque le système " -"n'utilise pas le mode d'accès simple au contenu" +"Assigner l'abonnement spécifié au système enregistré, lorsque le système " +"n'utilise pas le mode Simple Content Access" #: src/subscription_manager/cli_command/attach.py:111 msgid "Error: --auto may not be used when specifying pools." @@ -1864,9 +1867,9 @@ msgid "" "Ignoring the request to attach. Attaching subscriptions is disabled for " "organization \"{owner_id}\" because Simple Content Access (SCA) is enabled." msgstr "" -"Ignorer la demande de rattachement. Le rattachement des abonnements est " -"désactivé pour l'organisation \"{owner_id}\" car l'accès simple au contenu " -"(SCA) est activé." +"Demande d'assignation ignorée. L'assignation des abonnements est désactivée " +"pour l'organisation \"{owner_id}\" car Simple Content Access (SCA) est " +"activé." # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: src/subscription_manager/cli_command/attach.py:201 @@ -1979,9 +1982,9 @@ msgid "" "Ignoring the request to auto-attach. Attaching subscriptions is disabled for " "organization \"{owner_id}\" because Simple Content Access (SCA) is enabled." msgstr "" -"Ignorer la demande de rattachement automatique. Le rattachement des " -"abonnements est désactivé pour l'organisation \"{owner_id}\" car l'accès " -"simple au contenu (SCA) est activé." +"Demande d'assignation automatique ignorée. L'assignation des abonnements est " +"désactivée pour l'organisation \"{owner_id}\" car Simple Content Access (SCA)" +" est activé." #: src/subscription_manager/cli_command/cli.py:152 msgid "server URL in the form of https://hostname:port/prefix" @@ -2022,8 +2025,8 @@ msgstr "Ne pas afficher les messages de progression" msgid "" "Consumer identity either does not exist or is corrupted. Try register --help" msgstr "" -"L'identité du consommateur n'existe pas ou est corrompue. Essayez la " -"commande register --help" +"L'identité du consommateur n'existe pas ou est corrompue. Essayez register " +"--help" #: src/subscription_manager/cli_command/cli.py:267 msgid "" @@ -2064,7 +2067,8 @@ msgstr "" # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/cli_command/cli.py:422 msgid "System certificates corrupted. Please reregister." -msgstr "Certificats système corrompus. Veuillez réinscrire le système." +msgstr "" +"Certificats système corrompus. Veuillez effectuer l'enregistrement à nouveau." #: src/subscription_manager/cli_command/cli.py:433 #, python-brace-format @@ -2182,8 +2186,8 @@ msgstr "liste des environnements non activés pour ce consommateur" #: src/subscription_manager/cli_command/environments.py:81 msgid "You may not specify an --org for environments when registered." msgstr "" -"Vous ne pouvez pas spécifier un --org pour les environnements lors de " -"l'enregistrement." +"Vous ne pouvez pas spécifier un --org pour les environnements une fois " +"l'enregistrement effectué." # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: src/subscription_manager/cli_command/environments.py:90 @@ -2365,8 +2369,8 @@ msgid "" "Error: You may not import certificates into a system that is registered to a " "subscription management service." msgstr "" -"Erreur : vous ne pouvez pas importer de certificats dans un système qui est " -"déjà inscrit à un service de gestion des abonnements." +"Erreur : Vous ne pouvez pas importer de certificats dans un système qui est " +"déjà enregistré auprès d'un service de gestion des abonnements." # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: src/subscription_manager/cli_command/import_cert.py:52 @@ -2996,8 +3000,7 @@ msgstr "Référentiel : {repo}" #: src/subscription_manager/cli_command/owners.py:34 msgid "Display the organizations against which a user can register a system" msgstr "" -"Afficher les organisations avec lesquelles un utilisateur peut inscrire un " -"système" +"Afficher les organisations disponibles pour l'enregistrement d'un système" # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: src/subscription_manager/cli_command/owners.py:52 @@ -3036,7 +3039,7 @@ msgstr "énumérer les emplacements de greffons {SM}" #: src/subscription_manager/cli_command/plugins.py:37 #, python-brace-format msgid "list {SM} plugin hooks" -msgstr "énumérer les points d'ancrage des greffons {SM}" +msgstr "énumérer les hooks du plugin {SM}" # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: src/subscription_manager/cli_command/plugins.py:40 @@ -3115,7 +3118,7 @@ msgstr "" # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/cli_command/register.py:81 msgid "name of the system to register, defaults to the hostname" -msgstr "nom du système à inscrire, par défaut un nom d'hôte" +msgstr "nom du système à enregistrer, par défaut : nom d'hôte" # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/cli_command/register.py:87 @@ -3128,8 +3131,8 @@ msgid "" "register with one of multiple organizations for the user, using organization " "key" msgstr "" -"inscrire sur une des multiples organisations pour l'utilisateur, à l'aide de " -"la clé de l'organisation" +"enregistrer auprès d'une des organisations de l'utilisateur à l'aide d'une " +"clé d'organisation" #: src/subscription_manager/cli_command/register.py:99 msgid "" @@ -3137,10 +3140,10 @@ msgid "" "(a comma-separated list) in the destination org. The ability to use multiple " "environments is controlled by the entitlement server" msgstr "" -"s'inscrire à un environnement spécifique (valeur unique) ou à plusieurs " -"environnements (liste séparée par des virgules) dans l'org de destination. " -"La possibilité d'utiliser plusieurs environnements est contrôlée par le " -"serveur d'autorisation" +"enregistrer auprès d'un environnement spécifique (valeur unique) ou de " +"plusieurs environnements (liste séparée par des virgules) dans " +"l'organisation de destination. La possibilité d'utiliser plusieurs " +"environnements est contrôlée par le serveur de droits" # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/cli_command/register.py:107 @@ -3183,7 +3186,7 @@ msgstr "" # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/cli_command/register.py:140 msgid "This system is already registered. Use --force to override" -msgstr "Ce système est déjà inscrit. Utilisez --force pour remplacer" +msgstr "Ce système est déjà enregistré. Utilisez --force pour passer outre" # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/cli_command/register.py:152 @@ -3225,7 +3228,7 @@ msgstr "Annulation de l’inscription de : {hostname}:{port}{prefix}" #: src/subscription_manager/cli_command/register.py:301 #, python-brace-format msgid "The system with UUID {old_uuid} has been unregistered" -msgstr "Le système avec l'UUID {old_uuid} a été désinscrit" +msgstr "Le système avec l'UUID {old_uuid} a été désenregistré" #: src/subscription_manager/cli_command/register.py:325 #, python-brace-format @@ -3236,17 +3239,17 @@ msgstr "Inscription sur : {hostname}:{port}{prefix}" #: src/subscription_manager/cli_command/register.py:365 #, python-brace-format msgid "Error during registration: {e}" -msgstr "Erreur lors de l'inscription : {e}" +msgstr "Erreur lors de l'enregistrement : {e}" #: src/subscription_manager/cli_command/register.py:368 #, python-brace-format msgid "The system has been registered with ID: {id}" -msgstr "Le système a été inscrit avec l'ID : {id}" +msgstr "Le système a été enregistré avec l'ID : {id}" #: src/subscription_manager/cli_command/register.py:369 #, python-brace-format msgid "The registered system name is: {name}" -msgstr "Le nom du système inscrit est : {name}" +msgstr "Le nom de système enregistré est : {name}" #: src/subscription_manager/cli_command/register.py:371 #, python-brace-format @@ -3386,9 +3389,8 @@ msgid "" "Error: The registered entitlement server does not support remove --pool.\n" "Instead, use the remove --serial option." msgstr "" -"Erreur : Le serveur de droits d'accès utilisé ne prend pas en charge remove " -"--pool.\n" -"Veuillez plutôt utiliser l'option remove --serial." +"Erreur : Le serveur de droits utilisé ne prend pas en charge remove --pool.\n" +"Utilisez plutôt l'option remove --serial." #: src/subscription_manager/cli_command/remove.py:87 msgid "" @@ -3531,12 +3533,12 @@ msgstr "Le référentiel « {repoid} » est désactivé pour ce système." #: src/subscription_manager/cli_command/role.py:24 msgid "Show or modify the system purpose role setting" -msgstr "Afficher ou modifier les paramètres de rôle de l'objet du système" +msgstr "Afficher ou modifier les paramètres de rôle de la finalité du système" #: src/subscription_manager/cli_command/service_level.py:42 msgid "Show or modify the system purpose service-level setting" msgstr "" -"Afficher ou modifier le paramètre niveau de service de l'objet du système" +"Afficher ou modifier le paramètre niveau de service de la finalité du système" #: src/subscription_manager/cli_command/service_level.py:70 msgid "Error: --org is only supported with the --list or --set option" @@ -3549,8 +3551,8 @@ msgid "" "Error: you must register or specify --username and --password to list " "service levels" msgstr "" -"Erreur : vous devez inscrire le système ou spécifier --username et --" -"password pour répertorier les niveaux de service" +"Erreur : Vous devez effectuer l'enregistrement ou spécifier --username et --" +"password pour lister les niveaux de service" #: src/subscription_manager/cli_command/service_level.py:99 msgid "" @@ -3624,7 +3626,7 @@ msgid "" "Content Access Mode is set to Simple Content Access. This host has access to " "content, regardless of subscription status.\n" msgstr "" -"Le mode d'accès au contenu est réglé sur Simple Content Access. Cet hôte a " +"Le mode d'accès au contenu est défini sur Simple Content Access. Cet hôte a " "accès au contenu, quel que soit le statut de l'abonnement.\n" #: src/subscription_manager/cli_command/status.py:111 @@ -3639,15 +3641,16 @@ msgstr "" #: src/subscription_manager/cli_command/status.py:145 #, python-brace-format msgid "System Purpose Status: {status}" -msgstr "État de l'objet du système : {status}" +msgstr "État de la finalité du système : {status}" #: src/subscription_manager/cli_command/syspurpose.py:53 msgid "Convenient module for managing all system purpose settings" -msgstr "Module pratique pour gérer tous les paramètres de l'objet du système" +msgstr "" +"Module pratique pour gérer tous les paramètres de la finalité du système" #: src/subscription_manager/cli_command/syspurpose.py:59 msgid "show current system purpose" -msgstr "montrer l'objet du système actuel" +msgstr "montrer la finalité du système actuel" # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/cli_command/syspurpose.py:76 @@ -3669,16 +3672,17 @@ msgstr "%(prog)s {name} [SOUS-MODULE] [OPTIONS]" #: src/subscription_manager/cli_command/unregister.py:38 #: src/subscription_manager/utils.py:286 msgid "This system is currently not registered." -msgstr "Ce système n'est pas actuellement inscrit." +msgstr "Ce système n'est actuellement pas enregistré." # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: src/subscription_manager/cli_command/unregister.py:71 msgid "System has been unregistered." -msgstr "Le système a été désinscrit." +msgstr "Le système a été désenregistré." #: src/subscription_manager/cli_command/usage.py:24 msgid "Show or modify the system purpose usage setting" -msgstr "Afficher ou modifier le paramètre d'utilisation de l'objet du système" +msgstr "" +"Afficher ou modifier le paramètre d'utilisation de la finalité du système" # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: src/subscription_manager/cli_command/user_pass.py:40 @@ -3876,7 +3880,7 @@ msgstr "" # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: src/subscription_manager/exceptions.py:55 msgid "Server URL can not be None" -msgstr "L'URL du serveur ne peut pas être « Aucun »" +msgstr "L'URL du serveur ne peut pas être None" # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author samfreemanz #: src/subscription_manager/exceptions.py:56 diff --git a/po/it.po b/po/it.po index f76498039b..2d886f3549 100644 --- a/po/it.po +++ b/po/it.po @@ -2,13 +2,14 @@ # ljanda , 2018. #zanata # John Sefler , 2019. #zanata # Pino Toscano , 2021, 2022, 2023. +# Salvatore Cocuzza , 2024. msgid "" msgstr "" "Project-Id-Version: rhsm\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-07-21 12:27+0200\n" -"PO-Revision-Date: 2023-05-12 10:21+0000\n" -"Last-Translator: Pino Toscano \n" +"PO-Revision-Date: 2024-12-23 23:38+0000\n" +"Last-Translator: Salvatore Cocuzza \n" "Language-Team: Italian \n" "Language: it\n" @@ -16,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.15.2\n" +"X-Generator: Weblate 5.9.2\n" #: /usr/lib64/python3.9/argparse.py:296 msgid "usage: " @@ -3046,13 +3047,13 @@ msgstr "" # translation auto-copied from project subscription-manager, version 1.9.X, document keys #: src/subscription_manager/cli_command/redeem.py:43 -#, fuzzy msgid "" "optional language to use for email notification when subscription redemption " "is complete (Examples: en-us, de-de)" msgstr "" -"lingua facoltativa da usare per la notifica via posta elettronica quando " -"l'acquisizione della sottoscrizione è stata completata (esempi: en-us, de-de)" +"lingua facoltativa da usare per la notifica delle email quando " +"l'acquisizione della sottoscrizione è stata completata. (Esempi: en-us, de-" +"de)" # translation auto-copied from project subscription-manager, version 1.9.X, document keys, author fvalen #: src/subscription_manager/cli_command/redeem.py:52 diff --git a/po/ja.po b/po/ja.po index 5eed3b89cd..48e8097eb2 100644 --- a/po/ja.po +++ b/po/ja.po @@ -7,13 +7,14 @@ # Sundeep Anand , 2021, 2022. # Pino Toscano , 2021, 2023. # Transtats , 2022, 2023. +# 김인수 , 2024. msgid "" msgstr "" "Project-Id-Version: rhsm\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-07-21 12:27+0200\n" -"PO-Revision-Date: 2023-11-04 01:48+0000\n" -"Last-Translator: John Sefler \n" +"PO-Revision-Date: 2024-02-22 10:35+0000\n" +"Last-Translator: 김인수 \n" "Language-Team: Japanese \n" "Language: ja\n" @@ -21,7 +22,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.1.1\n" +"X-Generator: Weblate 5.4\n" #: /usr/lib64/python3.9/argparse.py:296 msgid "usage: " @@ -331,9 +332,8 @@ msgid "Unable to read consumer identity" msgstr "コンシューマー識別子を読み込めません" #: src/plugins/dnf/subscription_manager.py:141 -#, fuzzy msgid "subscription-manager is operating in container mode." -msgstr "サブスクリプションマネージャーがコンテナーモードで動作しています。" +msgstr "subscription-managerがコンテナーモードで動作しています。" #: src/plugins/dnf/upload_profile.py:37 msgid "" diff --git a/po/ka.po b/po/ka.po index 4124bbb424..c4a01fd7e3 100644 --- a/po/ka.po +++ b/po/ka.po @@ -1,13 +1,13 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the rhsm package. -# Temuri Doghonadze , 2022, 2023. +# Temuri Doghonadze , 2022, 2023, 2024. msgid "" msgstr "" "Project-Id-Version: rhsm\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-07-21 12:27+0200\n" -"PO-Revision-Date: 2023-05-04 04:16+0000\n" +"PO-Revision-Date: 2024-09-19 22:23+0000\n" "Last-Translator: Temuri Doghonadze \n" "Language-Team: Georgian \n" @@ -16,7 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.15.2\n" +"X-Generator: Weblate 5.7.2\n" #: /usr/lib64/python3.9/argparse.py:296 msgid "usage: " @@ -858,7 +858,7 @@ msgstr "ხელმისაწვდომი რელიზების გ #: src/rhsm/connection.py:2002 msgid "Fetching entitlements" -msgstr "გარემოების გამოთხოვა" +msgstr "გარემოს სიების გამოთხოვა" #: src/rhsm/connection.py:2011 msgid "Fetching service levels" diff --git a/po/keys.pot b/po/keys.pot index b62699cec4..ae71d336cf 100644 --- a/po/keys.pot +++ b/po/keys.pot @@ -8,226 +8,247 @@ msgid "" msgstr "" "Project-Id-Version: rhsm\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-21 12:27+0200\n" +"POT-Creation-Date: 2025-01-07 18:12+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" +"Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -#: /usr/lib64/python3.9/argparse.py:296 +#: /usr/lib64/python3.12/argparse.py:228 +#, python-format +msgid "%(heading)s:" +msgstr "" + +#: /usr/lib64/python3.12/argparse.py:299 msgid "usage: " msgstr "" -#: /usr/lib64/python3.9/argparse.py:861 +#: /usr/lib64/python3.12/argparse.py:722 +#, python-format +msgid " (default: %(default)s)" +msgstr "" + +#: /usr/lib64/python3.12/argparse.py:784 +#, python-format +msgid "argument %(argument_name)s: %(message)s" +msgstr "" + +#: /usr/lib64/python3.12/argparse.py:890 msgid ".__call__() not defined" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1204 +#: /usr/lib64/python3.12/argparse.py:1161 +msgid "show program's version number and exit" +msgstr "" + +#: /usr/lib64/python3.12/argparse.py:1223 +#, python-format +msgid "conflicting subparser: %s" +msgstr "" + +#: /usr/lib64/python3.12/argparse.py:1227 +#, python-format +msgid "conflicting subparser alias: %s" +msgstr "" + +#: /usr/lib64/python3.12/argparse.py:1262 #, python-format msgid "unknown parser %(parser_name)r (choices: %(choices)s)" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1264 +#: /usr/lib64/python3.12/argparse.py:1323 #, python-format msgid "argument \"-\" with mode %r" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1273 +#: /usr/lib64/python3.12/argparse.py:1332 #, python-format msgid "can't open '%(filename)s': %(error)s" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1482 +#: /usr/lib64/python3.12/argparse.py:1541 #, python-format msgid "cannot merge actions - two groups are named %r" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1520 +#: /usr/lib64/python3.12/argparse.py:1583 msgid "'required' is an invalid argument for positionals" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1542 +#: /usr/lib64/python3.12/argparse.py:1604 #, python-format msgid "" "invalid option string %(option)r: must start with a character " "%(prefix_chars)r" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1560 +#: /usr/lib64/python3.12/argparse.py:1622 #, python-format msgid "dest= is required for options like %r" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1577 +#: /usr/lib64/python3.12/argparse.py:1639 #, python-format msgid "invalid conflict_resolution value: %r" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1595 +#: /usr/lib64/python3.12/argparse.py:1657 #, python-format msgid "conflicting option string: %s" msgid_plural "conflicting option strings: %s" msgstr[0] "" msgstr[1] "" -#: /usr/lib64/python3.9/argparse.py:1661 +#: /usr/lib64/python3.12/argparse.py:1731 msgid "mutually exclusive arguments must be optional" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1728 +#: /usr/lib64/python3.12/argparse.py:1807 msgid "positional arguments" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1729 -msgid "optional arguments" +#: /usr/lib64/python3.12/argparse.py:1808 +msgid "options" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1744 +#: /usr/lib64/python3.12/argparse.py:1823 msgid "show this help message and exit" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1775 +#: /usr/lib64/python3.12/argparse.py:1854 msgid "cannot have multiple subparser arguments" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1827 /usr/lib64/python3.9/argparse.py:2338 +#: /usr/lib64/python3.12/argparse.py:1860 +msgid "subcommands" +msgstr "" + +#: /usr/lib64/python3.12/argparse.py:1906 +#: /usr/lib64/python3.12/argparse.py:2459 #, python-format msgid "unrecognized arguments: %s" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1928 +#: /usr/lib64/python3.12/argparse.py:2011 #, python-format msgid "not allowed with argument %s" msgstr "" -#: /usr/lib64/python3.9/argparse.py:1974 /usr/lib64/python3.9/argparse.py:1988 +#: /usr/lib64/python3.12/argparse.py:2030 +#, python-format +msgid "ambiguous option: %(option)s could match %(matches)s" +msgstr "" + +#: /usr/lib64/python3.12/argparse.py:2062 +#: /usr/lib64/python3.12/argparse.py:2094 #, python-format msgid "ignored explicit argument %r" msgstr "" -#: /usr/lib64/python3.9/argparse.py:2095 +#: /usr/lib64/python3.12/argparse.py:2230 #, python-format msgid "the following arguments are required: %s" msgstr "" -#: /usr/lib64/python3.9/argparse.py:2110 +#: /usr/lib64/python3.12/argparse.py:2245 #, python-format msgid "one of the arguments %s is required" msgstr "" -#: /usr/lib64/python3.9/argparse.py:2153 +#: /usr/lib64/python3.12/argparse.py:2289 msgid "expected one argument" msgstr "" -#: /usr/lib64/python3.9/argparse.py:2154 +#: /usr/lib64/python3.12/argparse.py:2290 msgid "expected at most one argument" msgstr "" -#: /usr/lib64/python3.9/argparse.py:2155 +#: /usr/lib64/python3.12/argparse.py:2291 msgid "expected at least one argument" msgstr "" -#: /usr/lib64/python3.9/argparse.py:2159 +#: /usr/lib64/python3.12/argparse.py:2295 #, python-format msgid "expected %s argument" msgid_plural "expected %s arguments" msgstr[0] "" msgstr[1] "" -#: /usr/lib64/python3.9/argparse.py:2217 -#, python-format -msgid "ambiguous option: %(option)s could match %(matches)s" -msgstr "" - -#: /usr/lib64/python3.9/argparse.py:2281 +#: /usr/lib64/python3.12/argparse.py:2405 #, python-format msgid "unexpected option string: %s" msgstr "" -#: /usr/lib64/python3.9/argparse.py:2478 +#: /usr/lib64/python3.12/argparse.py:2541 #, python-format msgid "%r is not callable" msgstr "" -#: /usr/lib64/python3.9/argparse.py:2495 +#: /usr/lib64/python3.12/argparse.py:2557 #, python-format msgid "invalid %(type)s value: %(value)r" msgstr "" -#: /usr/lib64/python3.9/argparse.py:2506 +#: /usr/lib64/python3.12/argparse.py:2572 #, python-format msgid "invalid choice: %(value)r (choose from %(choices)s)" msgstr "" -#: /usr/lib64/python3.9/argparse.py:2582 +#: /usr/lib64/python3.12/argparse.py:2650 #, python-format msgid "%(prog)s: error: %(message)s\n" msgstr "" -#: src/daemons/rhsmcertd.c:107 -msgid "deprecated, see --cert-check-interval" -msgstr "" - -#: src/daemons/rhsmcertd.c:110 +#: src/daemons/rhsmcertd.c:99 msgid "interval to run cert check (in minutes)" msgstr "" -#: src/daemons/rhsmcertd.c:114 -msgid "deprecated, see --auto-attach-interval" -msgstr "" - -#: src/daemons/rhsmcertd.c:117 -msgid "interval to run auto-attach (in minutes)" -msgstr "" - -#: src/daemons/rhsmcertd.c:120 +#: src/daemons/rhsmcertd.c:102 msgid "interval to run auto-registration (in minutes)" msgstr "" -#: src/daemons/rhsmcertd.c:123 +#: src/daemons/rhsmcertd.c:105 msgid "run the initial checks immediately, with no delay" msgstr "" -#: src/daemons/rhsmcertd.c:126 +#: src/daemons/rhsmcertd.c:108 msgid "show debug messages" msgstr "" -#: src/daemons/rhsmcertd.c:128 +#: src/daemons/rhsmcertd.c:110 msgid "do not add an offset to the initial checks." msgstr "" -#: src/daemons/rhsmcertd.c:131 +#: src/daemons/rhsmcertd.c:113 msgid "try to perform auto-registration." msgstr "" -#: src/daemons/rhsmcertd.c:612 -#, c-format +#: src/daemons/rhsmcertd.c:545 msgid "For more information run: rhsmcertd --help\n" msgstr "" -#: src/daemons/rhsmcertd.c:729 +#: src/daemons/rhsmcertd.c:651 msgid "Wrong number of arguments specified.\n" msgstr "" -#: src/daemons/rhsmcertd.c:805 +#: src/daemons/rhsmcertd.c:720 msgid "Invalid argument specified.\n" msgstr "" -#: src/daemons/rhsmcertd.c:811 -#, c-format +#: src/daemons/rhsmcertd.c:726 msgid "WARN: Deprecated CLI arguments are being used.\n" msgstr "" -#: src/daemons/rhsmcertd.c:826 +#: src/daemons/rhsmcertd.c:741 #, c-format msgid "Invalid option: %s\n" msgstr "" -#: src/daemons/rhsmcertd.c:840 +#: src/daemons/rhsmcertd.c:755 #, c-format msgid "Invalid argument specified: %s\n" msgstr "" @@ -236,7 +257,7 @@ msgstr "" msgid "Installed products updated." msgstr "" -#: src/plugins/dnf/subscription_manager.py:35 +#: src/plugins/dnf/subscription_manager.py:46 #, python-format msgid "" "\n" @@ -249,25 +270,40 @@ msgid "" "active subscriptions, you can renew the expired subscription. " msgstr "" -#: src/plugins/dnf/subscription_manager.py:46 +#: src/plugins/dnf/subscription_manager.py:57 msgid "" "\n" "This system is not registered with an entitlement server. You can use " "subscription-manager to register.\n" msgstr "" -#: src/plugins/dnf/subscription_manager.py:52 +#: src/plugins/dnf/subscription_manager.py:63 +msgid "" +"\n" +"This system is not registered with an entitlement server. You can use " +"\"rhc\" or \"subscription-manager\" to register.\n" +msgstr "" + +#: src/plugins/dnf/subscription_manager.py:70 msgid "" "\n" "This system is registered with an entitlement server, but is not receiving " "updates. You can use subscription-manager to assign subscriptions.\n" msgstr "" -#: src/plugins/dnf/subscription_manager.py:87 +#: src/plugins/dnf/subscription_manager.py:77 +#, python-brace-format +msgid "" +"\n" +"This system has release set to {release_version} and it receives updates " +"only for this release.\n" +msgstr "" + +#: src/plugins/dnf/subscription_manager.py:112 msgid "Not root, Subscription Management repositories not updated" msgstr "" -#: src/plugins/dnf/subscription_manager.py:115 +#: src/plugins/dnf/subscription_manager.py:140 #, python-format msgid "" "subscription-manager plugin disabled %d system repository with respect of " @@ -278,15 +314,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: src/plugins/dnf/subscription_manager.py:133 +#: src/plugins/dnf/subscription_manager.py:158 msgid "Updating Subscription Management repositories." msgstr "" -#: src/plugins/dnf/subscription_manager.py:138 +#: src/plugins/dnf/subscription_manager.py:167 msgid "Unable to read consumer identity" msgstr "" -#: src/plugins/dnf/subscription_manager.py:141 +#: src/plugins/dnf/subscription_manager.py:170 msgid "subscription-manager is operating in container mode." msgstr "" @@ -743,124 +779,115 @@ msgstr "" msgid "Unknown Certificate Type" msgstr "" -#: src/rhsm/config.py:244 +#: src/rhsm/config.py:243 #, python-brace-format msgid "Invalid Log Level: {lvl}, setting to INFO for this run." msgstr "" -#: src/rhsm/config.py:249 +#: src/rhsm/config.py:248 msgid "" "Please use: subscription-manager config --logging.default_log_level= to set the default_log_level to a valid value." msgstr "" -#: src/rhsm/config.py:255 +#: src/rhsm/config.py:254 #, python-brace-format msgid "Valid Values: {valid_str}" msgstr "" -#: src/rhsm/connection.py:1402 +#: src/rhsm/connection.py:1444 msgid "Fetching supported resources" msgstr "" -#: src/rhsm/connection.py:1461 +#: src/rhsm/connection.py:1504 msgid "Checking connection status" msgstr "" -#: src/rhsm/connection.py:1489 +#: src/rhsm/connection.py:1529 msgid "Fetching cloud token" msgstr "" -#: src/rhsm/connection.py:1562 +#: src/rhsm/connection.py:1608 msgid "Registering system" msgstr "" -#: src/rhsm/connection.py:1592 src/rhsm/connection.py:1604 +#: src/rhsm/connection.py:1638 src/rhsm/connection.py:1650 msgid "Updating detected virtual machines running on given host" msgstr "" -#: src/rhsm/connection.py:1627 +#: src/rhsm/connection.py:1673 msgid "Updating hypervisor information" msgstr "" -#: src/rhsm/connection.py:1702 +#: src/rhsm/connection.py:1745 msgid "Updating consumer information" msgstr "" -#: src/rhsm/connection.py:1707 src/rhsm/connection.py:1711 +#: src/rhsm/connection.py:1750 src/rhsm/connection.py:1754 msgid "Fetching guest information" msgstr "" -#: src/rhsm/connection.py:1715 +#: src/rhsm/connection.py:1758 msgid "Removing guests" msgstr "" -#: src/rhsm/connection.py:1745 src/rhsm/connection.py:1756 +#: src/rhsm/connection.py:1788 src/rhsm/connection.py:1799 msgid "Updating profile information" msgstr "" -#: src/rhsm/connection.py:1764 +#: src/rhsm/connection.py:1807 msgid "Fetching consumer keys" msgstr "" -#: src/rhsm/connection.py:1773 +#: src/rhsm/connection.py:1816 msgid "Checking compliance status" msgstr "" -#: src/rhsm/connection.py:1782 +#: src/rhsm/connection.py:1825 msgid "Checking system purpose compliance status" msgstr "" -#: src/rhsm/connection.py:1789 +#: src/rhsm/connection.py:1832 msgid "Fetching available system purpose settings" msgstr "" -#: src/rhsm/connection.py:1796 src/rhsm/connection.py:1804 +#: src/rhsm/connection.py:1839 src/rhsm/connection.py:1847 msgid "Fetching organizations" msgstr "" -#: src/rhsm/connection.py:1817 +#: src/rhsm/connection.py:1860 msgid "Unregistering system" msgstr "" -#: src/rhsm/connection.py:1830 +#: src/rhsm/connection.py:1884 msgid "Fetching certificates" msgstr "" -#: src/rhsm/connection.py:1839 +#: src/rhsm/connection.py:1893 msgid "Fetching certificate serial numbers" msgstr "" -#: src/rhsm/connection.py:1855 +#: src/rhsm/connection.py:1909 msgid "Fetching content for a certificate" msgstr "" -#: src/rhsm/connection.py:1868 src/rhsm/connection.py:1885 -msgid "Updating subscriptions" -msgstr "" - -#: src/rhsm/connection.py:1894 src/rhsm/connection.py:1904 -#: src/rhsm/connection.py:1913 -msgid "Unsubscribing" -msgstr "" - -#: src/rhsm/connection.py:1962 +#: src/rhsm/connection.py:1959 msgid "Fetching pools" msgstr "" -#: src/rhsm/connection.py:1973 +#: src/rhsm/connection.py:1970 msgid "Fetching release information" msgstr "" -#: src/rhsm/connection.py:1987 +#: src/rhsm/connection.py:1984 msgid "Fetching available releases" msgstr "" -#: src/rhsm/connection.py:2002 +#: src/rhsm/connection.py:1999 msgid "Fetching entitlements" msgstr "" -#: src/rhsm/connection.py:2011 +#: src/rhsm/connection.py:2008 msgid "Fetching service levels" msgstr "" @@ -892,16 +919,12 @@ msgstr "" msgid "Removing content overrides" msgstr "" -#: src/rhsm/connection.py:2118 -msgid "Activating" -msgstr "" - -#: src/rhsm/connection.py:2127 +#: src/rhsm/connection.py:2111 msgid "Fetching job" msgstr "" #: src/rhsm_debug/debug_commands.py:39 -#: src/subscription_manager/cli_command/cli.py:51 +#: src/subscription_manager/cli_command/cli.py:50 msgid "" "This system is not yet registered. Try 'subscription-manager register --" "help' for more information." @@ -954,13 +977,13 @@ msgstr "" msgid "Unable to create zip file of system information: {error}" msgstr "" -#: src/rhsmlib/dbus/objects/register.py:133 +#: src/rhsmlib/dbus/objects/register.py:156 #, python-format msgid "" "User %s is member of more organizations, but no organization was selected" msgstr "" -#: src/rhsmlib/dbus/objects/register.py:149 +#: src/rhsmlib/dbus/objects/register.py:172 #, python-format msgid "User %s is not member of any organization" msgstr "" @@ -995,41 +1018,36 @@ msgstr "" #: src/rhsmlib/services/entitlement.py:94 #: src/rhsmlib/services/syspurpose.py:107 #: src/subscription_manager/cert_sorter.py:296 -#: src/subscription_manager/cli_command/cli.py:233 -#: src/subscription_manager/cli_command/cli.py:237 -#: src/subscription_manager/cli_command/cli.py:238 -#: src/subscription_manager/cli_command/cli.py:239 +#: src/subscription_manager/cli_command/cli.py:213 +#: src/subscription_manager/cli_command/cli.py:217 +#: src/subscription_manager/cli_command/cli.py:218 +#: src/subscription_manager/cli_command/cli.py:219 #: src/subscription_manager/cli_command/facts.py:66 -#: src/subscription_manager/cli_command/list.py:54 #: src/subscription_manager/reasons.py:110 -#: src/subscription_manager/utils.py:269 src/subscription_manager/utils.py:285 -#: src/subscription_manager/utils.py:287 src/subscription_manager/utils.py:307 -#: src/subscription_manager/utils.py:309 src/subscription_manager/utils.py:328 +#: src/subscription_manager/utils.py:273 src/subscription_manager/utils.py:289 +#: src/subscription_manager/utils.py:291 src/subscription_manager/utils.py:311 +#: src/subscription_manager/utils.py:313 src/subscription_manager/utils.py:332 msgid "Unknown" msgstr "" -#: src/rhsmlib/services/entitlement.py:124 src/rhsmlib/services/register.py:61 +#: src/rhsmlib/services/entitlement.py:124 src/rhsmlib/services/register.py:69 #, python-format msgid "Unknown arguments: %s" msgstr "" #: src/rhsmlib/services/entitlement.py:289 -#: src/subscription_manager/cli_command/list.py:428 msgid "Virtual" msgstr "" #: src/rhsmlib/services/entitlement.py:291 -#: src/subscription_manager/cli_command/list.py:430 msgid "Physical" msgstr "" #: src/rhsmlib/services/entitlement.py:294 -#: src/subscription_manager/cli_command/list.py:433 msgid "Yes" msgstr "" #: src/rhsmlib/services/entitlement.py:296 -#: src/subscription_manager/cli_command/list.py:435 msgid "No" msgstr "" @@ -1056,32 +1074,26 @@ msgid "" msgstr "" #: src/rhsmlib/services/entitlement.py:497 -#: src/subscription_manager/cli_command/list.py:292 msgid "Error: --all is only applicable with --available" msgstr "" #: src/rhsmlib/services/entitlement.py:499 -#: src/subscription_manager/cli_command/list.py:294 msgid "Error: --ondate is only applicable with --available" msgstr "" #: src/rhsmlib/services/entitlement.py:504 -#: src/subscription_manager/cli_command/list.py:297 msgid "Error: --servicelevel is only applicable with --available or --consumed" msgstr "" #: src/rhsmlib/services/entitlement.py:508 -#: src/subscription_manager/cli_command/list.py:302 msgid "Error: --match-installed is only applicable with --available" msgstr "" #: src/rhsmlib/services/entitlement.py:511 -#: src/subscription_manager/cli_command/list.py:304 msgid "Error: --no-overlap is only applicable with --available" msgstr "" #: src/rhsmlib/services/entitlement.py:516 -#: src/subscription_manager/cli_command/list.py:307 msgid "" "Error: --pool-only is only applicable with --available and/or --consumed" msgstr "" @@ -1090,44 +1102,61 @@ msgstr "" msgid "Error: this system is not registered" msgstr "" -#: src/rhsmlib/services/register.py:192 +#: src/rhsmlib/services/register.py:73 +msgid "Environment IDs and environment names are mutually exclusive" +msgstr "" + +#: src/rhsmlib/services/register.py:174 +#, python-brace-format +msgid "" +"Environment: '{env_names}' does not have required type '{environment_type}'" +msgstr "" + +#: src/rhsmlib/services/register.py:182 +#, python-brace-format +msgid "" +"Environments: '{env_names}' do not have required type '{environment_type}'" +msgstr "" + +#: src/rhsmlib/services/register.py:202 +msgid "" +"Registration is only possible when the organization is in Simple Content " +"Access (SCA) mode." +msgstr "" + +#: src/rhsmlib/services/register.py:243 msgid "This system is already registered. Add force to options to override." msgstr "" -#: src/rhsmlib/services/register.py:195 -#: src/subscription_manager/cli_command/register.py:142 +#: src/rhsmlib/services/register.py:246 +#: src/subscription_manager/cli_command/register.py:121 msgid "Error: system name can not be empty." msgstr "" -#: src/rhsmlib/services/register.py:199 -#: src/subscription_manager/cli_command/register.py:164 +#: src/rhsmlib/services/register.py:250 +#: src/subscription_manager/cli_command/register.py:139 msgid "" "Error: Can not force registration while attempting to recover registration " "with consumerid. Please use --force without --consumerid to re-register or " "use the clean command and try again without --force." msgstr "" -#: src/rhsmlib/services/register.py:209 -#: src/subscription_manager/cli_command/register.py:155 +#: src/rhsmlib/services/register.py:260 +#: src/subscription_manager/cli_command/register.py:132 msgid "Error: Must specify an activation key" msgstr "" -#: src/rhsmlib/services/register.py:211 -#: src/subscription_manager/cli_command/register.py:144 +#: src/rhsmlib/services/register.py:262 +#: src/subscription_manager/cli_command/register.py:123 msgid "Error: Activation keys do not require user credentials." msgstr "" -#: src/rhsmlib/services/register.py:214 -#: src/subscription_manager/cli_command/register.py:147 +#: src/rhsmlib/services/register.py:265 +#: src/subscription_manager/cli_command/register.py:126 msgid "Error: Activation keys can not be used with previously registered IDs." msgstr "" -#: src/rhsmlib/services/register.py:218 -#: src/subscription_manager/cli_command/register.py:150 -msgid "Error: Activation keys do not allow environments to be specified." -msgstr "" - -#: src/rhsmlib/services/register.py:225 +#: src/rhsmlib/services/register.py:271 msgid "Error: Missing username or password." msgstr "" @@ -1154,39 +1183,39 @@ msgstr "" msgid "Disabled" msgstr "" -#: src/subscription_manager/branding/__init__.py:77 +#: src/subscription_manager/branding/__init__.py:76 msgid "Register the system to the server" msgstr "" -#: src/subscription_manager/branding/__init__.py:78 +#: src/subscription_manager/branding/__init__.py:77 msgid "Unregister the system from the server" msgstr "" -#: src/subscription_manager/branding/__init__.py:79 +#: src/subscription_manager/branding/__init__.py:78 msgid "This system is registered to spacewalk" msgstr "" -#: src/subscription_manager/branding/__init__.py:81 +#: src/subscription_manager/branding/__init__.py:80 #: src/subscription_manager/branding/redhat_branding.py:14 -#: src/subscription_manager/managercli.py:96 +#: src/subscription_manager/managercli.py:78 msgid "WARNING" msgstr "" -#: src/subscription_manager/branding/__init__.py:81 +#: src/subscription_manager/branding/__init__.py:80 msgid "You have already registered with spacewalk." msgstr "" -#: src/subscription_manager/branding/__init__.py:84 +#: src/subscription_manager/branding/__init__.py:83 msgid "Please enter your account information:" msgstr "" -#: src/subscription_manager/branding/__init__.py:86 +#: src/subscription_manager/branding/__init__.py:85 msgid "" "Contact your system administrator if you have forgotten your login or " "password" msgstr "" -#: src/subscription_manager/branding/__init__.py:88 +#: src/subscription_manager/branding/__init__.py:87 #: src/subscription_manager/branding/redhat_branding.py:29 msgid "Red Hat Subscription Management" msgstr "" @@ -1257,7 +1286,7 @@ msgid "" "forgot_password" msgstr "" -#: src/subscription_manager/cache.py:176 +#: src/subscription_manager/cache.py:178 #, python-brace-format msgid "" "consumer_uuid={consumer_uuid} is not a valid consumer_uuid. Not attempting " @@ -1350,29 +1379,25 @@ msgstr "" msgid "--add cannot be used with --remove" msgstr "" -#: src/subscription_manager/cli_command/abstract_syspurpose.py:151 -msgid "Error: you can specify --username or --token not both" -msgstr "" - -#: src/subscription_manager/cli_command/abstract_syspurpose.py:156 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:152 #, python-brace-format msgid "" "Error: you must register or specify --username and --password to list {attr}" msgstr "" -#: src/subscription_manager/cli_command/abstract_syspurpose.py:173 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:167 msgid "" -"Error: --username, --password, --token and --org can be used only on " -"unregistered systems" +"Error: --username, --password, and --org can be used only on unregistered " +"systems" msgstr "" #. TRANSLATORS: this is used to quote a string -#: src/subscription_manager/cli_command/abstract_syspurpose.py:251 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:239 #, python-brace-format msgid "\"{value}\"" msgstr "" -#: src/subscription_manager/cli_command/abstract_syspurpose.py:255 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:243 #, python-brace-format msgid "" "Warning: Provided value {vals} is not included in the list of valid values" @@ -1381,7 +1406,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: src/subscription_manager/cli_command/abstract_syspurpose.py:264 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:252 #, python-brace-format msgid "" "Warning: This organization does not have any subscriptions that provide a " @@ -1389,297 +1414,143 @@ msgid "" "subscriptions." msgstr "" -#: src/subscription_manager/cli_command/abstract_syspurpose.py:278 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:266 #, python-brace-format msgid "{attr} set to \"{val}\"." msgstr "" -#: src/subscription_manager/cli_command/abstract_syspurpose.py:296 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:284 #, python-brace-format msgid "{attr} unset." msgstr "" -#: src/subscription_manager/cli_command/abstract_syspurpose.py:313 -#: src/subscription_manager/cli_command/abstract_syspurpose.py:338 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:301 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:326 #, python-brace-format msgid "{attr} updated." msgstr "" -#: src/subscription_manager/cli_command/abstract_syspurpose.py:369 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:357 #, python-brace-format msgid "Current {name}: {val}" msgstr "" -#: src/subscription_manager/cli_command/abstract_syspurpose.py:371 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:359 #, python-brace-format msgid "{name} not set." msgstr "" -#: src/subscription_manager/cli_command/abstract_syspurpose.py:389 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:377 #, python-brace-format msgid "Available {syspurpose_attr}" msgstr "" -#: src/subscription_manager/cli_command/abstract_syspurpose.py:401 -#: src/subscription_manager/cli_command/service_level.py:213 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:389 +#: src/subscription_manager/cli_command/service_level.py:210 #, python-brace-format msgid "" "There are no available values for the system purpose \"{syspurpose_attr}\" " "from the available subscriptions in this organization." msgstr "" -#: src/subscription_manager/cli_command/abstract_syspurpose.py:409 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:397 #, python-brace-format msgid "" "Unable to get the list of valid values for the system purpose " "\"{syspurpose_attr}\"." msgstr "" -#: src/subscription_manager/cli_command/abstract_syspurpose.py:435 -msgid "Unable to connect to server using token" -msgstr "" - -#: src/subscription_manager/cli_command/abstract_syspurpose.py:483 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:461 #: src/subscription_manager/i18n_argparse.py:35 #, python-format msgid "%(prog)s [OPTIONS]" msgstr "" -#: src/subscription_manager/cli_command/abstract_syspurpose.py:492 +#: src/subscription_manager/cli_command/abstract_syspurpose.py:470 #, python-brace-format msgid "" "Note: The currently configured entitlement server does not support System " "Purpose {attr}." msgstr "" -#: src/subscription_manager/cli_command/addons.py:24 -msgid "Show or modify the system purpose addons setting" -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:51 -msgid "The ID of the pool to attach (can be specified more than once)" -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:57 -msgid "Number of subscriptions to attach. May not be used with an auto-attach." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:63 -msgid "" -"Automatically attach the best-matched compatible subscriptions to this " -"system. This is the default action." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:71 -msgid "" -"Automatically attach only subscriptions matching the specified service " -"level; only used with --auto" -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:79 -msgid "" -"A file from which to read pool IDs. If a hyphen is provided, pool IDs will " -"be read from stdin." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:85 -msgid "All installed products are covered by valid entitlements." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:86 -msgid "No need to update subscriptions at this time." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:98 -msgid "" -"Attach a specified subscription to the registered system, when system does " -"not use Simple Content Access mode" -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:111 -msgid "Error: --auto may not be used when specifying pools." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:114 -msgid "Error: The --servicelevel option cannot be used when specifying pools." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:120 -msgid "Error: Quantity must be a positive integer." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:122 -msgid "Error: --quantity may not be used with an auto-attach" -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:132 -msgid "Error: Received data does not contain any pool IDs." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:136 -#, python-brace-format -msgid "Error: The file \"{file}\" does not contain any pool IDs." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:143 -#, python-brace-format -msgid "Error: The file \"{file}\" does not exist or cannot be read." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:157 -#, python-brace-format -msgid "" -"Ignoring the request to attach. Attaching subscriptions is disabled for " -"organization \"{owner_id}\" because Simple Content Access (SCA) is enabled." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:201 -msgid "Please enter a valid numeric pool ID." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:209 -#, python-brace-format -msgid "Successfully attached a subscription for: {name}" -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:239 -msgid "No Installed products on system. No need to attach subscriptions." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:243 -msgid "" -"All installed products are covered by valid entitlements. No need to update " -"subscriptions at this time." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:257 -#: src/subscription_manager/cli_command/register.py:201 -msgid "" -"Error: The --servicelevel option is not supported by the server. Did not " -"complete your request." -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:271 -msgid "Service level set to: {}" -msgstr "" - -#: src/subscription_manager/cli_command/attach.py:280 -msgid "Entitlement Certificate(s) update failed due to the following reasons:" -msgstr "" - -#: src/subscription_manager/cli_command/autoheal.py:27 -msgid "Set if subscriptions are attached on a schedule (default of daily)" -msgstr "" - -#: src/subscription_manager/cli_command/autoheal.py:28 -msgid "specify whether to enable or disable auto-attaching of subscriptions" -msgstr "" - -#: src/subscription_manager/cli_command/autoheal.py:35 -msgid "try to attach subscriptions for uncovered products each check-in" -msgstr "" - -#: src/subscription_manager/cli_command/autoheal.py:41 -msgid "do not try to automatically attach subscriptions each check-in" -msgstr "" - -#: src/subscription_manager/cli_command/autoheal.py:47 -msgid "show the current auto-attach preference" -msgstr "" - -#: src/subscription_manager/cli_command/autoheal.py:60 -msgid "Auto-attach preference: enabled" -msgstr "" - -#: src/subscription_manager/cli_command/autoheal.py:62 -msgid "Auto-attach preference: disabled" -msgstr "" - #: src/subscription_manager/cli_command/clean.py:25 msgid "" "Remove all local system and subscription data without affecting the server" msgstr "" #: src/subscription_manager/cli_command/clean.py:31 -#: src/subscription_manager/cli_command/register.py:312 +#: src/subscription_manager/cli_command/register.py:256 msgid "All local data removed" msgstr "" -#: src/subscription_manager/cli_command/cli.py:131 -#, python-brace-format -msgid "" -"Ignoring the request to auto-attach. Attaching subscriptions is disabled for " -"organization \"{owner_id}\" because Simple Content Access (SCA) is enabled." -msgstr "" - -#: src/subscription_manager/cli_command/cli.py:152 +#: src/subscription_manager/cli_command/cli.py:132 msgid "server URL in the form of https://hostname:port/prefix" msgstr "" -#: src/subscription_manager/cli_command/cli.py:159 +#: src/subscription_manager/cli_command/cli.py:139 msgid "" "do not check the entitlement server SSL certificate against available " "certificate authorities" msgstr "" -#: src/subscription_manager/cli_command/cli.py:170 +#: src/subscription_manager/cli_command/cli.py:150 msgid "proxy URL in the form of hostname:port" msgstr "" -#: src/subscription_manager/cli_command/cli.py:176 +#: src/subscription_manager/cli_command/cli.py:156 msgid "user for HTTP proxy with basic authentication" msgstr "" -#: src/subscription_manager/cli_command/cli.py:182 +#: src/subscription_manager/cli_command/cli.py:162 msgid "password for HTTP proxy with basic authentication" msgstr "" -#: src/subscription_manager/cli_command/cli.py:188 +#: src/subscription_manager/cli_command/cli.py:168 msgid "host suffixes that should bypass HTTP proxy" msgstr "" -#: src/subscription_manager/cli_command/cli.py:197 +#: src/subscription_manager/cli_command/cli.py:177 msgid "Do not display progress messages" msgstr "" -#: src/subscription_manager/cli_command/cli.py:209 +#: src/subscription_manager/cli_command/cli.py:189 msgid "" "Consumer identity either does not exist or is corrupted. Try register --help" msgstr "" -#: src/subscription_manager/cli_command/cli.py:267 +#: src/subscription_manager/cli_command/cli.py:247 msgid "" "subscription-manager is operating in container mode. Use your host system to " "manage subscriptions.\n" msgstr "" -#: src/subscription_manager/cli_command/cli.py:282 +#: src/subscription_manager/cli_command/cli.py:262 #, python-brace-format msgid "{prog}: error: no such option: {args}" msgstr "" -#: src/subscription_manager/cli_command/cli.py:301 +#: src/subscription_manager/cli_command/cli.py:281 msgid "Error parsing serverurl:" msgstr "" -#: src/subscription_manager/cli_command/cli.py:317 +#: src/subscription_manager/cli_command/cli.py:297 msgid "Error parsing baseurl:" msgstr "" -#: src/subscription_manager/cli_command/cli.py:390 +#: src/subscription_manager/cli_command/cli.py:369 #, python-brace-format msgid "Unable to reach the server at {host}:{port}{handler}" msgstr "" -#: src/subscription_manager/cli_command/cli.py:400 +#: src/subscription_manager/cli_command/cli.py:379 msgid "Error: CA certificate for subscription service has not been installed." msgstr "" -#: src/subscription_manager/cli_command/cli.py:422 +#: src/subscription_manager/cli_command/cli.py:401 msgid "System certificates corrupted. Please reregister." msgstr "" -#: src/subscription_manager/cli_command/cli.py:433 +#: src/subscription_manager/cli_command/cli.py:412 #, python-brace-format msgid "" "Consumer profile \"{uuid}\" has been deleted from the server. You can use " @@ -1740,72 +1611,81 @@ msgstr "" msgid "Section {section} and name {name} cannot be removed." msgstr "" -#: src/subscription_manager/cli_command/environments.py:44 +#: src/subscription_manager/cli_command/environments.py:42 +#: src/subscription_manager/cli_command/owners.py:31 +msgid "Name:" +msgstr "" + +#: src/subscription_manager/cli_command/environments.py:43 +msgid "Description:" +msgstr "" + +#: src/subscription_manager/cli_command/environments.py:49 msgid "Display the environments available for a user" msgstr "" -#: src/subscription_manager/cli_command/environments.py:45 +#: src/subscription_manager/cli_command/environments.py:50 msgid "specify organization for environment list, using organization key" msgstr "" -#: src/subscription_manager/cli_command/environments.py:52 +#: src/subscription_manager/cli_command/environments.py:57 msgid "set an ordered comma-separated list of environments for this consumer" msgstr "" -#: src/subscription_manager/cli_command/environments.py:58 +#: src/subscription_manager/cli_command/environments.py:63 msgid "list all environments for the organization" msgstr "" -#: src/subscription_manager/cli_command/environments.py:65 +#: src/subscription_manager/cli_command/environments.py:70 msgid "list the environments enabled for this consumer in order" msgstr "" -#: src/subscription_manager/cli_command/environments.py:72 +#: src/subscription_manager/cli_command/environments.py:77 msgid "list the environments not enabled for this consumer" msgstr "" -#: src/subscription_manager/cli_command/environments.py:81 +#: src/subscription_manager/cli_command/environments.py:86 msgid "You may not specify an --org for environments when registered." msgstr "" -#: src/subscription_manager/cli_command/environments.py:90 -#: src/subscription_manager/cli_command/register.py:460 +#: src/subscription_manager/cli_command/environments.py:95 +#: src/subscription_manager/cli_command/register.py:387 msgid "Error: Server does not support environments." msgstr "" -#: src/subscription_manager/cli_command/environments.py:97 +#: src/subscription_manager/cli_command/environments.py:99 msgid "This operation requires user credentials" msgstr "" -#: src/subscription_manager/cli_command/environments.py:111 +#: src/subscription_manager/cli_command/environments.py:113 msgid "Error: Unable to retrieve environment list from server" msgstr "" -#: src/subscription_manager/cli_command/environments.py:126 +#: src/subscription_manager/cli_command/environments.py:128 msgid "Environments updated." msgstr "" -#: src/subscription_manager/cli_command/environments.py:128 +#: src/subscription_manager/cli_command/environments.py:130 msgid "Error: Server does not support environment updates." msgstr "" -#: src/subscription_manager/cli_command/environments.py:136 +#: src/subscription_manager/cli_command/environments.py:138 msgid "Error: Server does not support multi-environment operations." msgstr "" -#: src/subscription_manager/cli_command/environments.py:154 +#: src/subscription_manager/cli_command/environments.py:156 msgid "Environments" msgstr "" -#: src/subscription_manager/cli_command/environments.py:167 +#: src/subscription_manager/cli_command/environments.py:169 msgid "This list operation does not have any environments to report." msgstr "" -#: src/subscription_manager/cli_command/environments.py:203 +#: src/subscription_manager/cli_command/environments.py:205 msgid "Error: The same environment may not be listed more than once. " msgstr "" -#: src/subscription_manager/cli_command/environments.py:209 +#: src/subscription_manager/cli_command/environments.py:211 #, python-brace-format msgid "No such environment: {names}" msgid_plural "No such environments: {names}" @@ -1850,479 +1730,108 @@ msgstr "" msgid "--username and --password can only be used with --force" msgstr "" -#: src/subscription_manager/cli_command/identity.py:65 -msgid "--token can only be used with --force" -msgstr "" - -#: src/subscription_manager/cli_command/identity.py:74 -#: src/subscription_manager/cli_command/identity.py:77 +#: src/subscription_manager/cli_command/identity.py:72 +#: src/subscription_manager/cli_command/identity.py:75 #: src/subscription_manager/cli_command/version.py:33 #, python-brace-format msgid "server type: {type}" msgstr "" -#: src/subscription_manager/cli_command/identity.py:89 +#: src/subscription_manager/cli_command/identity.py:87 #, python-brace-format msgid "system identity: {consumerid}" msgstr "" -#: src/subscription_manager/cli_command/identity.py:90 +#: src/subscription_manager/cli_command/identity.py:88 #, python-brace-format msgid "name: {consumer_name}" msgstr "" -#: src/subscription_manager/cli_command/identity.py:91 +#: src/subscription_manager/cli_command/identity.py:89 #, python-brace-format msgid "org name: {ownername}" msgstr "" -#: src/subscription_manager/cli_command/identity.py:92 +#: src/subscription_manager/cli_command/identity.py:90 #, python-brace-format msgid "org ID: {ownerid}" msgstr "" -#: src/subscription_manager/cli_command/identity.py:107 +#: src/subscription_manager/cli_command/identity.py:105 #: src/subscription_manager/printing_utils.py:165 #: src/subscription_manager/printing_utils.py:184 msgid "None" msgstr "" -#: src/subscription_manager/cli_command/identity.py:110 +#: src/subscription_manager/cli_command/identity.py:108 #, python-brace-format msgid "environment name: {environment_name}" msgid_plural "environment names: {environment_name}" msgstr[0] "" msgstr[1] "" -#: src/subscription_manager/cli_command/identity.py:130 +#: src/subscription_manager/cli_command/identity.py:125 msgid "Identity certificate has been regenerated." msgstr "" -#: src/subscription_manager/cli_command/identity.py:143 +#: src/subscription_manager/cli_command/identity.py:138 msgid "Error: Unable to generate a new identity for the system" msgstr "" -#: src/subscription_manager/cli_command/import_cert.py:30 -msgid "Import certificates which were provided outside of the tool" -msgstr "" - -#: src/subscription_manager/cli_command/import_cert.py:37 -msgid "certificate file to import (can be specified more than once)" -msgstr "" - -#: src/subscription_manager/cli_command/import_cert.py:45 -msgid "" -"Error: You may not import certificates into a system that is registered to a " -"subscription management service." -msgstr "" - -#: src/subscription_manager/cli_command/import_cert.py:52 -msgid "" -"Error: This command requires that you specify a certificate with --" -"certificate." -msgstr "" - -#: src/subscription_manager/cli_command/import_cert.py:69 -#, python-brace-format -msgid "Successfully imported certificate {file}" -msgstr "" - -#: src/subscription_manager/cli_command/import_cert.py:81 -#, python-brace-format -msgid "{file} is not a valid certificate file. Please use a valid certificate." -msgstr "" - -#: src/subscription_manager/cli_command/import_cert.py:90 -msgid "" -"An error occurred while importing the certificate. Please check log file for " -"more information." -msgstr "" - -#: src/subscription_manager/cli_command/import_cert.py:96 -#, python-brace-format -msgid "{file}: file not found." -msgstr "" - -#: src/subscription_manager/cli_command/list.py:49 -msgid "Future Subscription" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:50 -msgid "Subscribed" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:51 -msgid "Not Subscribed" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:52 -msgid "Expired" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:53 -msgid "Partially Subscribed" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:58 -#: src/subscription_manager/cli_command/list.py:69 -#: src/subscription_manager/cli_command/list.py:111 +#: src/subscription_manager/cli_command/list.py:29 msgid "Product Name:" msgstr "" -#: src/subscription_manager/cli_command/list.py:59 -#: src/subscription_manager/cli_command/list.py:70 +#: src/subscription_manager/cli_command/list.py:30 msgid "Product ID:" msgstr "" -#: src/subscription_manager/cli_command/list.py:60 -#: src/subscription_manager/cli_command/list.py:71 +#: src/subscription_manager/cli_command/list.py:31 msgid "Version:" msgstr "" -#: src/subscription_manager/cli_command/list.py:61 -#: src/subscription_manager/cli_command/list.py:72 +#: src/subscription_manager/cli_command/list.py:32 msgid "Arch:" msgstr "" -#: src/subscription_manager/cli_command/list.py:62 -#: src/subscription_manager/cli_command/list.py:112 -msgid "Status:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:63 -#: src/subscription_manager/cli_command/list.py:138 -#: src/subscription_manager/cli_command/list.py:161 -msgid "Status Details:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:64 -#: src/subscription_manager/cli_command/list.py:90 -#: src/subscription_manager/cli_command/list.py:140 -#: src/subscription_manager/cli_command/list.py:163 -msgid "Starts:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:65 -#: src/subscription_manager/cli_command/list.py:91 -#: src/subscription_manager/cli_command/list.py:141 -#: src/subscription_manager/cli_command/list.py:164 -msgid "Ends:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:76 -#: src/subscription_manager/cli_command/list.py:96 -#: src/subscription_manager/cli_command/list.py:126 -#: src/subscription_manager/cli_command/list.py:146 -msgid "Subscription Name:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:77 -#: src/subscription_manager/cli_command/list.py:97 -#: src/subscription_manager/cli_command/list.py:127 -#: src/subscription_manager/cli_command/list.py:147 -msgid "Provides:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:78 -#: src/subscription_manager/cli_command/list.py:98 -#: src/subscription_manager/cli_command/list.py:128 -#: src/subscription_manager/cli_command/list.py:148 -msgid "SKU:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:79 -#: src/subscription_manager/cli_command/list.py:99 -#: src/subscription_manager/cli_command/list.py:129 -#: src/subscription_manager/cli_command/list.py:149 -msgid "Contract:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:80 -#: src/subscription_manager/cli_command/list.py:132 -#: src/subscription_manager/cli_command/list.py:152 -msgid "Pool ID:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:81 -#: src/subscription_manager/cli_command/list.py:133 -#: src/subscription_manager/cli_command/list.py:153 -msgid "Provides Management:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:82 -msgid "Available:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:83 -msgid "Suggested:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:84 -#: src/subscription_manager/cli_command/list.py:136 -#: src/subscription_manager/cli_command/list.py:156 -msgid "Service Type:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:85 -#: src/subscription_manager/cli_command/list.py:157 -msgid "Roles:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:86 -#: src/subscription_manager/cli_command/list.py:100 -#: src/subscription_manager/cli_command/list.py:137 -#: src/subscription_manager/cli_command/list.py:158 -msgid "Service Level:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:87 -#: src/subscription_manager/cli_command/list.py:159 -msgid "Usage:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:88 -#: src/subscription_manager/cli_command/list.py:160 -msgid "Add-ons:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:89 -#: src/subscription_manager/cli_command/list.py:139 -#: src/subscription_manager/cli_command/list.py:162 -msgid "Subscription Type:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:92 -#: src/subscription_manager/cli_command/list.py:165 -msgid "Entitlement Type:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:104 -msgid "Repo ID:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:105 -msgid "Repo Name:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:106 -msgid "Repo URL:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:107 -msgid "Enabled:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:116 -#: src/subscription_manager/cli_command/list.py:121 -msgid "Name:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:117 -msgid "Description:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:122 -msgid "Key:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:130 -#: src/subscription_manager/cli_command/list.py:150 -msgid "Account:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:131 -#: src/subscription_manager/cli_command/list.py:151 -msgid "Serial:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:134 -#: src/subscription_manager/cli_command/list.py:154 -msgid "Active:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:135 -#: src/subscription_manager/cli_command/list.py:155 -msgid "Quantity Used:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:142 -msgid "System Type:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:187 -msgid "No products installed." -msgstr "" - -#: src/subscription_manager/cli_command/list.py:191 -msgid "Installed Product Current Status:" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:202 -msgid "Unable to find available subscriptions for all your installed products." -msgstr "" - -#: src/subscription_manager/cli_command/list.py:208 +#: src/subscription_manager/cli_command/list.py:41 msgid "List subscription and product information for this system" msgstr "" -#: src/subscription_manager/cli_command/list.py:215 +#: src/subscription_manager/cli_command/list.py:46 msgid "list shows those products which are installed (default)" msgstr "" -#: src/subscription_manager/cli_command/list.py:220 -msgid "show those subscriptions which are available" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:225 -msgid "used with --available to ensure all subscriptions are returned" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:231 -#, python-brace-format -msgid "" -"date to search on, defaults to today's date, only used with --available " -"(example: {example})" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:237 -msgid "show the subscriptions being consumed by this system" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:243 -msgid "" -"shows only subscriptions matching the specified service level; only used " -"with --available and --consumed" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:251 -msgid "" -"shows pools which provide products that are not already covered; only used " -"with --available" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:259 -msgid "" -"shows only subscriptions matching products that are currently installed; " -"only used with --available" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:267 -msgid "" -"lists only subscriptions or products containing the specified expression in " -"the subscription or product information, varying with the list requested and " -"the server version (case-insensitive)." -msgstr "" - -#: src/subscription_manager/cli_command/list.py:277 -msgid "" -"lists only the pool IDs for applicable available or consumed subscriptions; " -"only used with --available and --consumed" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:285 -#, python-brace-format -msgid "" -"show pools that are active on or after the given date; only used with --" -"available (example: {example})" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:310 -msgid "Error: --afterdate is only applicable with --available" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:312 -msgid "Error: --afterdate cannot be used with --ondate" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:327 -#, python-brace-format +#: src/subscription_manager/cli_command/list.py:51 msgid "" -"Date entered is invalid. Date should be in YYYY-MM-DD format (example: " -"{dateexample})" +"lists only products containing the specified expression in the product " +"information." msgstr "" -#: src/subscription_manager/cli_command/list.py:355 +#: src/subscription_manager/cli_command/list.py:64 msgid " Installed Product Status" msgstr "" -#: src/subscription_manager/cli_command/list.py:391 +#: src/subscription_manager/cli_command/list.py:82 #, python-brace-format msgid "No installed products were found matching the expression \"{filter}\"." msgstr "" -#: src/subscription_manager/cli_command/list.py:396 +#: src/subscription_manager/cli_command/list.py:87 msgid "No installed products to list" msgstr "" -#: src/subscription_manager/cli_command/list.py:423 -msgid "Available Subscriptions" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:471 -#, python-brace-format -msgid "" -"No available subscription pools were found matching the expression " -"\"{filter}\" and the service level \"{level}\"." -msgstr "" - -#: src/subscription_manager/cli_command/list.py:478 -#, python-brace-format -msgid "" -"No available subscription pools were found matching the expression " -"\"{filter}\"." -msgstr "" - -#: src/subscription_manager/cli_command/list.py:484 -#, python-brace-format -msgid "" -"No available subscription pools were found matching the service level " -"\"{level}\"." -msgstr "" - -#: src/subscription_manager/cli_command/list.py:488 -msgid "No available subscription pools to list" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:510 -msgid "Consumed Subscriptions" -msgstr "" - -#: src/subscription_manager/cli_command/list.py:535 -#, python-brace-format -msgid "" -"No consumed subscription pools were found matching the expression " -"\"{filter}\" and the service level \"{level}\"." -msgstr "" - -#: src/subscription_manager/cli_command/list.py:541 -#, python-brace-format -msgid "" -"No consumed subscription pools were found matching the expression " -"\"{filter}\"." -msgstr "" - -#: src/subscription_manager/cli_command/list.py:548 -#, python-brace-format -msgid "" -"No consumed subscription pools were found matching the service level " -"\"{level}\"." -msgstr "" - -#: src/subscription_manager/cli_command/list.py:552 -msgid "No consumed subscription pools were found." -msgstr "" - #: src/subscription_manager/cli_command/org.py:35 msgid "specify an organization" msgstr "" #: src/subscription_manager/cli_command/org.py:46 -#: src/subscription_manager/cli_command/register.py:522 +#: src/subscription_manager/cli_command/register.py:449 msgid "Error: --org is a required parameter in non-interactive mode." msgstr "" #: src/subscription_manager/cli_command/org.py:48 -#: src/subscription_manager/cli_command/register.py:527 +#: src/subscription_manager/cli_command/register.py:454 msgid "Organization: " msgstr "" @@ -2332,7 +1841,7 @@ msgid "Error: User {username} is not member of any organization." msgstr "" #: src/subscription_manager/cli_command/org.py:74 -#: src/subscription_manager/cli_command/register.py:513 +#: src/subscription_manager/cli_command/register.py:440 #, python-brace-format msgid "Hint: User \"{name}\" is member of following organizations: {orgs}" msgstr "" @@ -2399,7 +1908,7 @@ msgid "Error: The 'repo-override' command is not supported by the server." msgstr "" #: src/subscription_manager/cli_command/override.py:138 -#: src/subscription_manager/cli_command/repos.py:124 +#: src/subscription_manager/cli_command/repos.py:130 msgid "Repositories disabled by configuration." msgstr "" @@ -2424,20 +1933,24 @@ msgstr "" msgid "Repository: {repo}" msgstr "" -#: src/subscription_manager/cli_command/owners.py:34 +#: src/subscription_manager/cli_command/owners.py:32 +msgid "Key:" +msgstr "" + +#: src/subscription_manager/cli_command/owners.py:38 msgid "Display the organizations against which a user can register a system" msgstr "" -#: src/subscription_manager/cli_command/owners.py:52 +#: src/subscription_manager/cli_command/owners.py:53 msgid "Organizations" msgstr "" -#: src/subscription_manager/cli_command/owners.py:61 +#: src/subscription_manager/cli_command/owners.py:62 #, python-brace-format msgid "{username} cannot register with any organizations." msgstr "" -#: src/subscription_manager/cli_command/owners.py:69 +#: src/subscription_manager/cli_command/owners.py:70 msgid "Error: Unable to retrieve org list from server" msgstr "" @@ -2472,29 +1985,6 @@ msgstr "" msgid "enabled" msgstr "" -#: src/subscription_manager/cli_command/redeem.py:29 -msgid "Attempt to redeem a subscription for a preconfigured system" -msgstr "" - -#: src/subscription_manager/cli_command/redeem.py:36 -msgid "email address to notify when subscription redemption is complete" -msgstr "" - -#: src/subscription_manager/cli_command/redeem.py:43 -msgid "" -"optional language to use for email notification when subscription redemption " -"is complete (Examples: en-us, de-de)" -msgstr "" - -#: src/subscription_manager/cli_command/redeem.py:52 -msgid "" -"Error: This command requires that you specify an email address with --email." -msgstr "" - -#: src/subscription_manager/cli_command/redeem.py:74 -msgid "Error: Unable to redeem subscription for this system." -msgstr "" - #: src/subscription_manager/cli_command/refresh.py:32 msgid "Pull the latest subscription data from the server" msgstr "" @@ -2512,142 +2002,120 @@ msgstr "" msgid "All local data refreshed" msgstr "" -#: src/subscription_manager/cli_command/register.py:68 +#: src/subscription_manager/cli_command/register.py:64 msgid "base URL for content in form of https://hostname:port/prefix" msgstr "" -#: src/subscription_manager/cli_command/register.py:81 +#: src/subscription_manager/cli_command/register.py:77 msgid "name of the system to register, defaults to the hostname" msgstr "" -#: src/subscription_manager/cli_command/register.py:87 +#: src/subscription_manager/cli_command/register.py:83 msgid "the existing system data is pulled from the server" msgstr "" -#: src/subscription_manager/cli_command/register.py:93 +#: src/subscription_manager/cli_command/register.py:89 msgid "" "register with one of multiple organizations for the user, using organization " "key" msgstr "" -#: src/subscription_manager/cli_command/register.py:99 +#: src/subscription_manager/cli_command/register.py:95 msgid "" "register with a specific environment (single value) or multiple environments " "(a comma-separated list) in the destination org. The ability to use multiple " "environments is controlled by the entitlement server" msgstr "" -#: src/subscription_manager/cli_command/register.py:107 +#: src/subscription_manager/cli_command/register.py:103 msgid "set a release version" msgstr "" -#: src/subscription_manager/cli_command/register.py:112 -msgid "Deprecated, see --auto-attach" -msgstr "" - -#: src/subscription_manager/cli_command/register.py:118 -msgid "automatically attach compatible subscriptions to this system" -msgstr "" - -#: src/subscription_manager/cli_command/register.py:123 +#: src/subscription_manager/cli_command/register.py:108 msgid "" "include an implicit attempt to unregister before registering a new system " "identity" msgstr "" -#: src/subscription_manager/cli_command/register.py:129 +#: src/subscription_manager/cli_command/register.py:114 msgid "" "activation key to use for registration (can be specified more than once)" msgstr "" -#: src/subscription_manager/cli_command/register.py:134 -msgid "" -"system preference used when subscribing automatically, requires --auto-attach" -msgstr "" - -#: src/subscription_manager/cli_command/register.py:140 +#: src/subscription_manager/cli_command/register.py:119 msgid "This system is already registered. Use --force to override" msgstr "" -#: src/subscription_manager/cli_command/register.py:152 -msgid "Error: Activation keys cannot be used with --auto-attach." -msgstr "" - -#: src/subscription_manager/cli_command/register.py:157 -msgid "Error: Must use --auto-attach with --servicelevel." +#: src/subscription_manager/cli_command/register.py:129 +msgid "Error: Activation keys do not allow environments to be specified." msgstr "" -#: src/subscription_manager/cli_command/register.py:159 +#: src/subscription_manager/cli_command/register.py:134 msgid "Error: Must provide --org with activation keys." msgstr "" -#: src/subscription_manager/cli_command/register.py:173 +#: src/subscription_manager/cli_command/register.py:148 msgid "Error: The --type option has been deprecated and may not be used." msgstr "" -#: src/subscription_manager/cli_command/register.py:176 -#: src/subscription_manager/cli_command/register.py:491 +#: src/subscription_manager/cli_command/register.py:151 +#: src/subscription_manager/cli_command/register.py:418 msgid "The entitlement server does not allow multiple environments" msgstr "" -#: src/subscription_manager/cli_command/register.py:220 +#: src/subscription_manager/cli_command/register.py:164 msgid "Uploading DNF profile" msgstr "" -#: src/subscription_manager/cli_command/register.py:290 +#: src/subscription_manager/cli_command/register.py:234 #: src/subscription_manager/cli_command/unregister.py:41 #, python-brace-format msgid "Unregistering from: {hostname}:{port}{prefix}" msgstr "" -#: src/subscription_manager/cli_command/register.py:301 +#: src/subscription_manager/cli_command/register.py:245 #, python-brace-format msgid "The system with UUID {old_uuid} has been unregistered" msgstr "" -#: src/subscription_manager/cli_command/register.py:325 +#: src/subscription_manager/cli_command/register.py:267 #, python-brace-format msgid "Registering to: {hostname}:{port}{prefix}" msgstr "" -#: src/subscription_manager/cli_command/register.py:365 +#: src/subscription_manager/cli_command/register.py:306 #, python-brace-format msgid "Error during registration: {e}" msgstr "" -#: src/subscription_manager/cli_command/register.py:368 +#: src/subscription_manager/cli_command/register.py:309 #, python-brace-format msgid "The system has been registered with ID: {id}" msgstr "" -#: src/subscription_manager/cli_command/register.py:369 +#: src/subscription_manager/cli_command/register.py:310 #, python-brace-format msgid "The registered system name is: {name}" msgstr "" -#: src/subscription_manager/cli_command/register.py:371 -#, python-brace-format -msgid "Service level set to: {level}" -msgstr "" - -#: src/subscription_manager/cli_command/register.py:438 +#: src/subscription_manager/cli_command/register.py:365 msgid "Error: --environments is a required parameter in non-interactive mode." msgstr "" -#: src/subscription_manager/cli_command/register.py:442 +#: src/subscription_manager/cli_command/register.py:369 msgid "Environments: " msgstr "" -#: src/subscription_manager/cli_command/register.py:444 +#: src/subscription_manager/cli_command/register.py:371 msgid "Environment: " msgstr "" -#: src/subscription_manager/cli_command/register.py:485 +#: src/subscription_manager/cli_command/register.py:412 #, python-brace-format msgid "Hint: Organization \"{key}\" contains following environments: {list}" msgstr "" -#: src/subscription_manager/cli_command/register.py:501 +#: src/subscription_manager/cli_command/register.py:428 #, python-brace-format msgid "{name} cannot register with any organizations." msgstr "" @@ -2708,147 +2176,77 @@ msgstr "" msgid "Available Releases" msgstr "" -#: src/subscription_manager/cli_command/remove.py:41 -msgid "certificate serial number to remove (can be specified more than once)" -msgstr "" - -#: src/subscription_manager/cli_command/remove.py:48 -msgid "the ID of the pool to remove (can be specified more than once)" -msgstr "" - -#: src/subscription_manager/cli_command/remove.py:54 -msgid "remove all subscriptions from this system" -msgstr "" - -#: src/subscription_manager/cli_command/remove.py:58 -msgid "Remove all or specific subscriptions from this system" -msgstr "" - -#: src/subscription_manager/cli_command/remove.py:71 -#, python-brace-format -msgid "Error: '{serial}' is not a valid serial number" -msgstr "" - -#: src/subscription_manager/cli_command/remove.py:80 -msgid "" -"Error: The registered entitlement server does not support remove --pool.\n" -"Instead, use the remove --serial option." -msgstr "" - -#: src/subscription_manager/cli_command/remove.py:87 -msgid "" -"Error: This command requires that you specify one of --serial, --pool or --" -"all." -msgstr "" - -#: src/subscription_manager/cli_command/remove.py:93 -msgid "The entitlement server successfully removed these pools:" -msgstr "" - -#: src/subscription_manager/cli_command/remove.py:95 -msgid "The entitlement server successfully removed these serial numbers:" -msgstr "" - -#: src/subscription_manager/cli_command/remove.py:97 -msgid "The entitlement server successfully removed these IDs:" -msgstr "" - -#: src/subscription_manager/cli_command/remove.py:102 -msgid "The entitlement server failed to remove these pools:" -msgstr "" - -#: src/subscription_manager/cli_command/remove.py:104 -msgid "The entitlement server failed to remove these serial numbers:" -msgstr "" - -#: src/subscription_manager/cli_command/remove.py:106 -msgid "The entitlement server failed to remove these IDs:" +#: src/subscription_manager/cli_command/repos.py:34 +msgid "Repo ID:" msgstr "" -#: src/subscription_manager/cli_command/remove.py:124 -msgid "All subscriptions have been removed at the server." +#: src/subscription_manager/cli_command/repos.py:35 +msgid "Repo Name:" msgstr "" -#: src/subscription_manager/cli_command/remove.py:129 -#, python-format -msgid "%s subscription removed at the server." -msgid_plural "%s subscriptions removed at the server." -msgstr[0] "" -msgstr[1] "" - -#: src/subscription_manager/cli_command/remove.py:170 -#: src/subscription_manager/cli_command/remove.py:206 -#, python-brace-format -msgid "Unable to perform remove due to the following exception: {e}" +#: src/subscription_manager/cli_command/repos.py:36 +msgid "Repo URL:" msgstr "" -#: src/subscription_manager/cli_command/remove.py:182 -#, python-brace-format -msgid "{total} subscription removed from this system." -msgid_plural "{total} subscriptions removed from this system." -msgstr[0] "" -msgstr[1] "" - -#: src/subscription_manager/cli_command/remove.py:198 -#, python-brace-format -msgid "Subscription with serial number {serial} removed from this system" +#: src/subscription_manager/cli_command/repos.py:37 +msgid "Enabled:" msgstr "" -#: src/subscription_manager/cli_command/repos.py:60 +#: src/subscription_manager/cli_command/repos.py:66 msgid "List the repositories which this system is entitled to use" msgstr "" -#: src/subscription_manager/cli_command/repos.py:67 +#: src/subscription_manager/cli_command/repos.py:73 msgid "list all known repositories for this system" msgstr "" -#: src/subscription_manager/cli_command/repos.py:73 +#: src/subscription_manager/cli_command/repos.py:79 msgid "list known, enabled repositories for this system" msgstr "" -#: src/subscription_manager/cli_command/repos.py:79 +#: src/subscription_manager/cli_command/repos.py:85 msgid "list known, disabled repositories for this system" msgstr "" -#: src/subscription_manager/cli_command/repos.py:87 +#: src/subscription_manager/cli_command/repos.py:93 msgid "" "repository to enable (can be specified more than once). Wildcards (* and ?) " "are supported." msgstr "" -#: src/subscription_manager/cli_command/repos.py:96 +#: src/subscription_manager/cli_command/repos.py:102 msgid "" "repository to disable (can be specified more than once). Wildcards (* and ?) " "are supported." msgstr "" -#: src/subscription_manager/cli_command/repos.py:165 +#: src/subscription_manager/cli_command/repos.py:171 #, python-brace-format msgid " Available Repositories in {file}" msgstr "" -#: src/subscription_manager/cli_command/repos.py:181 +#: src/subscription_manager/cli_command/repos.py:187 msgid "There were no available repositories matching the specified criteria." msgstr "" -#: src/subscription_manager/cli_command/repos.py:183 -#: src/subscription_manager/cli_command/repos.py:201 +#: src/subscription_manager/cli_command/repos.py:189 +#: src/subscription_manager/cli_command/repos.py:207 msgid "This system has no repositories available through subscriptions." msgstr "" -#: src/subscription_manager/cli_command/repos.py:210 +#: src/subscription_manager/cli_command/repos.py:216 #, python-brace-format msgid "" "Error: '{repoid}' does not match a valid repository ID. Use \"subscription-" "manager repos --list\" to see valid repositories." msgstr "" -#: src/subscription_manager/cli_command/repos.py:265 +#: src/subscription_manager/cli_command/repos.py:271 #, python-brace-format msgid "Repository '{repoid}' is enabled for this system." msgstr "" -#: src/subscription_manager/cli_command/repos.py:267 +#: src/subscription_manager/cli_command/repos.py:273 #, python-brace-format msgid "Repository '{repoid}' is disabled for this system." msgstr "" @@ -2871,42 +2269,42 @@ msgid "" "service levels" msgstr "" -#: src/subscription_manager/cli_command/service_level.py:99 +#: src/subscription_manager/cli_command/service_level.py:98 msgid "" -"Error: --username, --password, --token, --org and --serverurl can be used " -"only on unregistered systems" +"Error: --username, --password, --org and --serverurl can be used only on " +"unregistered systems" msgstr "" -#: src/subscription_manager/cli_command/service_level.py:126 +#: src/subscription_manager/cli_command/service_level.py:123 msgid "Error: Unable to retrieve service levels." msgstr "" -#: src/subscription_manager/cli_command/service_level.py:157 +#: src/subscription_manager/cli_command/service_level.py:154 #, python-brace-format msgid "Service level set to: \"{val}\"." msgstr "" -#: src/subscription_manager/cli_command/service_level.py:165 +#: src/subscription_manager/cli_command/service_level.py:162 msgid "Service level preference has been unset" msgstr "" -#: src/subscription_manager/cli_command/service_level.py:172 -#: src/subscription_manager/cli_command/service_level.py:188 -#: src/subscription_manager/cli_command/service_level.py:222 -#: src/subscription_manager/cli_command/service_level.py:229 +#: src/subscription_manager/cli_command/service_level.py:169 +#: src/subscription_manager/cli_command/service_level.py:185 +#: src/subscription_manager/cli_command/service_level.py:219 +#: src/subscription_manager/cli_command/service_level.py:226 msgid "Error: The service-level command is not supported by the server." msgstr "" -#: src/subscription_manager/cli_command/service_level.py:192 +#: src/subscription_manager/cli_command/service_level.py:189 #, python-brace-format msgid "Current service level: {level}" msgstr "" -#: src/subscription_manager/cli_command/service_level.py:194 +#: src/subscription_manager/cli_command/service_level.py:191 msgid "Service level preference not set" msgstr "" -#: src/subscription_manager/cli_command/service_level.py:206 +#: src/subscription_manager/cli_command/service_level.py:203 msgid "Available Service Levels" msgstr "" @@ -2942,30 +2340,30 @@ msgstr "" msgid "System Purpose Status: {status}" msgstr "" -#: src/subscription_manager/cli_command/syspurpose.py:53 +#: src/subscription_manager/cli_command/syspurpose.py:51 msgid "Convenient module for managing all system purpose settings" msgstr "" -#: src/subscription_manager/cli_command/syspurpose.py:59 +#: src/subscription_manager/cli_command/syspurpose.py:57 msgid "show current system purpose" msgstr "" -#: src/subscription_manager/cli_command/syspurpose.py:76 +#: src/subscription_manager/cli_command/syspurpose.py:74 #, python-brace-format msgid "{prog} {name}" msgstr "" -#: src/subscription_manager/cli_command/syspurpose.py:78 +#: src/subscription_manager/cli_command/syspurpose.py:76 msgid "Syspurpose submodules" msgstr "" -#: src/subscription_manager/cli_command/syspurpose.py:92 +#: src/subscription_manager/cli_command/syspurpose.py:90 #, python-format, python-brace-format msgid "%(prog)s {name} [SUBMODULE] [OPTIONS]" msgstr "" #: src/subscription_manager/cli_command/unregister.py:38 -#: src/subscription_manager/utils.py:286 +#: src/subscription_manager/utils.py:290 msgid "This system is currently not registered." msgstr "" @@ -2985,23 +2383,19 @@ msgstr "" msgid "password to use when authorizing against the server" msgstr "" -#: src/subscription_manager/cli_command/user_pass.py:50 -msgid "token to use when authorizing against the server" -msgstr "" - -#: src/subscription_manager/cli_command/user_pass.py:64 +#: src/subscription_manager/cli_command/user_pass.py:59 msgid "Error: --username is a required parameter in non-interactive mode." msgstr "" -#: src/subscription_manager/cli_command/user_pass.py:69 +#: src/subscription_manager/cli_command/user_pass.py:64 msgid "Error: --password is a required parameter in non-interactive mode." msgstr "" -#: src/subscription_manager/cli_command/user_pass.py:73 +#: src/subscription_manager/cli_command/user_pass.py:68 msgid "Username: " msgstr "" -#: src/subscription_manager/cli_command/user_pass.py:76 +#: src/subscription_manager/cli_command/user_pass.py:71 msgid "Password: " msgstr "" @@ -3019,34 +2413,34 @@ msgstr "" msgid "subscription management rules: {version}" msgstr "" -#: src/subscription_manager/entcertlib.py:295 +#: src/subscription_manager/entcertlib.py:294 #, python-format msgid "%s local certificate has been deleted." msgid_plural "%s local certificates have been deleted." msgstr[0] "" msgstr[1] "" -#: src/subscription_manager/entcertlib.py:452 +#: src/subscription_manager/entcertlib.py:451 #, python-format msgid "Total updates: %d" msgstr "" -#: src/subscription_manager/entcertlib.py:453 +#: src/subscription_manager/entcertlib.py:452 #, python-format msgid "Found (local) serial# %s" msgstr "" -#: src/subscription_manager/entcertlib.py:454 +#: src/subscription_manager/entcertlib.py:453 #, python-format msgid "Expected (UEP) serial# %s" msgstr "" -#: src/subscription_manager/entcertlib.py:455 +#: src/subscription_manager/entcertlib.py:454 #: src/subscription_manager/repolib.py:682 msgid "Added (new)" msgstr "" -#: src/subscription_manager/entcertlib.py:456 +#: src/subscription_manager/entcertlib.py:455 msgid "Deleted (rogue):" msgstr "" @@ -3118,7 +2512,6 @@ msgid "Bad CA certificate: {file}: {reason}" msgstr "" #: src/subscription_manager/exceptions.py:51 -#: src/subscription_manager/scripts/rhsmcertd_worker.py:218 msgid "Your identity certificate has expired" msgstr "" @@ -3278,15 +2671,25 @@ msgstr "" msgid "Error uploading package profile: %s\n" msgstr "" -#: src/subscription_manager/scripts/rhsmcertd_worker.py:198 -msgid "Updating entitlement certificates & repositories" +#: src/subscription_manager/scripts/rhsmcertd_worker.py:328 +msgid "" +"This system is already registered, ignoring request to automatically " +"register." +msgstr "" + +#: src/subscription_manager/scripts/rhsmcertd_worker.py:331 +msgid "Registering the system" +msgstr "" + +#: src/subscription_manager/scripts/rhsmcertd_worker.py:342 +msgid "Updating entitlement certificates & repositories." msgstr "" -#: src/subscription_manager/scripts/rhsmcertd_worker.py:293 +#: src/subscription_manager/scripts/rhsmcertd_worker.py:404 msgid "Unable to update entitlement certificates and repositories" msgstr "" -#: src/subscription_manager/utils.py:386 +#: src/subscription_manager/utils.py:390 msgid "and" msgstr "" diff --git a/po/ko.po b/po/ko.po index d9a88757e4..2b5d43e0b7 100644 --- a/po/ko.po +++ b/po/ko.po @@ -8,13 +8,13 @@ # simmon , 2021, 2022. # Pino Toscano , 2021. # Kim InSoo , 2022. -# 김인수 , 2022, 2023. +# 김인수 , 2022, 2023, 2024. msgid "" msgstr "" "Project-Id-Version: rhsm\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-07-21 12:27+0200\n" -"PO-Revision-Date: 2023-12-21 17:35+0000\n" +"PO-Revision-Date: 2024-09-27 06:11+0000\n" "Last-Translator: 김인수 \n" "Language-Team: Korean \n" @@ -23,7 +23,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.3\n" +"X-Generator: Weblate 5.7.2\n" #: /usr/lib64/python3.9/argparse.py:296 msgid "usage: " @@ -328,7 +328,7 @@ msgstr "소비자 ID를 읽을 수 없습니다" #: src/plugins/dnf/subscription_manager.py:141 msgid "subscription-manager is operating in container mode." -msgstr "서비스크립션-관리자는 컨테이너 방식에서 작동하고 있습니다." +msgstr "subscription-manager는 컨테이너 방식에서 작동하고 있습니다." #: src/plugins/dnf/upload_profile.py:37 msgid "" @@ -1387,7 +1387,7 @@ msgstr "계정 정보를 입력하십시오:" msgid "" "Contact your system administrator if you have forgotten your login or " "password" -msgstr "로그인이나 암호를 잊어버리신 경우 시스템 관리자에게 문의하십시오" +msgstr "로그인이나 비밀번호를 잊어버리신 경우 시스템 관리자에게 문의하십시오" # translation auto-copied from project subscription-manager, version 1.11.X, document keys #: src/subscription_manager/branding/__init__.py:88 @@ -1480,8 +1480,7 @@ msgstr "RHN Classic 및 Red Hat 서브스크립션 관리" msgid "" "Tip: Forgot your login or password? Look it up at https://redhat.com/" "forgot_password" -msgstr "" -"정보: 로그인 또는 암호를 잊어버리셨습니까? https://redhat.com/" +msgstr "정보: 로그인 또는 비밀번호 잊어버리셨습니까? https://redhat.com/" "forgot_password 에서 찾아보십시오" #: src/subscription_manager/cache.py:176 @@ -1947,7 +1946,8 @@ msgstr "소비자 ID가 없거나 손상되었습니다. register --help를 사 msgid "" "subscription-manager is operating in container mode. Use your host system to " "manage subscriptions.\n" -msgstr "서비스크립션-관리자는 컨테이너 방식에서 작동하고 있습니다. 자신의 호스트 " +msgstr "" +"subscription-manager는 컨테이너 방식에서 작동하고 있습니다. 자신의 호스트 " "시스템을 사용하여 서브스크립션을 관리하세요.\n" # translation auto-copied from project subscription-manager, version 1.11.X, document keys @@ -2966,7 +2966,7 @@ msgstr "오류: 이 명령에서는 --email을 사용하여 이메일 주소를 # translation auto-copied from project subscription-manager, version 1.11.X, document keys #: src/subscription_manager/cli_command/redeem.py:74 msgid "Error: Unable to redeem subscription for this system." -msgstr "오류: 이와 같은 시스템을 위해 서브스크립션을 상환 할 수 없습니다" +msgstr "오류: 이와 같은 시스템을 위해 서브스크립션을 상환 할 수 없습니다." # translation auto-copied from project subscription-manager, version 1.11.X, document keys #: src/subscription_manager/cli_command/refresh.py:32 diff --git a/po/ru.po b/po/ru.po index 10eedae8df..4528d2dad8 100644 --- a/po/ru.po +++ b/po/ru.po @@ -3,13 +3,14 @@ # ljanda , 2018. #zanata # John Sefler , 2019. #zanata, 2021. # ljanda , 2019. #zanata +# Aleksey Fedorov , 2024. msgid "" msgstr "" "Project-Id-Version: rhsm\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-07-21 12:27+0200\n" -"PO-Revision-Date: 2021-08-10 19:35+0000\n" -"Last-Translator: John Sefler \n" +"PO-Revision-Date: 2024-03-21 20:35+0000\n" +"Last-Translator: Aleksey Fedorov \n" "Language-Team: Russian \n" "Language: ru\n" @@ -18,7 +19,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 4.7.2\n" +"X-Generator: Weblate 5.4\n" #: /usr/lib64/python3.9/argparse.py:296 #, fuzzy @@ -727,7 +728,7 @@ msgstr "Версия" # translation auto-copied from project subscription-manager, version 1.11.X, document keys #: src/rct/printing.py:42 msgid "Arch" -msgstr "Арх." +msgstr "Архитектура" # translation auto-copied from project subscription-manager, version 1.11.X, document keys #: src/rct/printing.py:43 @@ -756,11 +757,11 @@ msgstr "" # translation auto-copied from project subscription-manager, version 1.11.X, document keys #: src/rct/printing.py:74 msgid "Usage" -msgstr "Формат" +msgstr "Использование" #: src/rct/printing.py:75 msgid "Add-ons" -msgstr "" +msgstr "Дополнения" # translation auto-copied from project subscription-manager, version 1.11.X, document keys, author ypoyarko #: src/rct/printing.py:78 src/subscription_manager/managerlib.py:431 diff --git a/scripts/container-pre-test.sh b/scripts/container-pre-test.sh index ad26215706..150f0c6c6e 100644 --- a/scripts/container-pre-test.sh +++ b/scripts/container-pre-test.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -euo pipefail + # Install essential packages dnf --setopt install_weak_deps=False install -y \ dnf-plugins-core git gcc cmake python3 python3-devel python3-pip @@ -13,7 +15,7 @@ if [[ $ID == "centos" ]]; then fi # Install system, build and runtime packages -dnf --setopt install_weak_deps=False install -y \ +dnf --setopt install_weak_deps=False install -y --nobest \ intltool python3-setuptools \ openssl-devel libdnf-devel \ python3-rpm python3-librepo python3-gobject \ diff --git a/setup.py b/setup.py index ced66e545d..f1f7bbe254 100755 --- a/setup.py +++ b/setup.py @@ -263,7 +263,7 @@ def find_py(self): setup( name="subscription-manager", - version="1.29.40", + version="1.30.3", url="http://www.candlepinproject.org", description="Manage subscriptions for Red Hat products.", license="GPLv2", @@ -275,6 +275,7 @@ def find_py(self): entry_points={ "console_scripts": [ "subscription-manager = subscription_manager.scripts.subscription_manager:main", + "package-profile-upload = subscription_manager.scripts.package_profile_upload:main", "rct = subscription_manager.scripts.rct:main", "rhsm-debug = subscription_manager.scripts.rhsm_debug:main", "rhsm-facts-service = subscription_manager.scripts.rhsm_facts_service:main", diff --git a/src/daemons/rhsmcertd.c b/src/daemons/rhsmcertd.c index b7568af375..58cde21ca0 100644 --- a/src/daemons/rhsmcertd.c +++ b/src/daemons/rhsmcertd.c @@ -47,7 +47,6 @@ typedef enum { #define LOGFILE LOGDIR"/rhsmcertd.log" #define LOCKFILE "/var/lock/subsys/rhsmcertd" #define NEXT_CERT_UPDATE_FILE "/run/rhsm/next_cert_check_update" -#define NEXT_AUTO_ATTACH_UPDATE_FILE "/run/rhsm/next_auto_attach_update" #define NEXT_AUTO_REGISTER_UPDATE_FILE "/run/rhsm/next_auto_register_update" #define WORKER LIBEXECDIR"/rhsmcertd-worker" #define WORKER_NAME WORKER @@ -55,7 +54,6 @@ typedef enum { #define INITIAL_DELAY_SECONDS 120 #define DEFAULT_AUTO_REG_INTERVAL_SECONDS 3600 /* 1 hour */ #define DEFAULT_CERT_INTERVAL_SECONDS 14400 /* 4 hours */ -#define DEFAULT_HEAL_INTERVAL_SECONDS 86400 /* 24 hours */ #define DEFAULT_SPLAY_ENABLED true #define DEFAULT_AUTO_REGISTRATION false #define DEFAULT_LOG_LEVEL LOG_LEVEL_INFO @@ -86,7 +84,6 @@ static LOG_LEVEL log_level = DEFAULT_LOG_LEVEL; static gboolean show_debug = FALSE; static gboolean run_now = FALSE; static gint arg_cert_interval_minutes = -1; -static gint arg_heal_interval_minutes = -1; static gint arg_reg_interval_minutes = -1; static gboolean arg_no_splay = FALSE; static gboolean arg_auto_registration = FALSE; @@ -94,25 +91,13 @@ static int fd_lock = -1; struct CertCheckData { int interval_seconds; - bool heal; char *next_update_file; }; static GOptionEntry entries[] = { - /* marked deprecated as of 02-19-2013, needs to be removed...? */ - {"cert-interval", 0, 0, G_OPTION_ARG_INT, &arg_heal_interval_minutes, - N_("deprecated, see --cert-check-interval"), - "MINUTES"}, {"cert-check-interval", 'c', 0, G_OPTION_ARG_INT, &arg_cert_interval_minutes, N_("interval to run cert check (in minutes)"), "MINUTES"}, - /* marked deprecated as of 11-16-2012, needs to be removed...? */ - {"heal-interval", 0, 0, G_OPTION_ARG_INT, &arg_heal_interval_minutes, - N_("deprecated, see --auto-attach-interval"), - "MINUTES"}, - {"auto-attach-interval", 'i', 0, G_OPTION_ARG_INT, &arg_heal_interval_minutes, - N_("interval to run auto-attach (in minutes)"), - "MINUTES"}, {"auto-registration-interval", 'r', 0, G_OPTION_ARG_INT, &arg_reg_interval_minutes, N_("interval to run auto-registration (in minutes)"), "MINUTES"}, @@ -132,7 +117,6 @@ static GOptionEntry entries[] = { typedef struct _Config { int auto_reg_interval_seconds; - int heal_interval_seconds; int cert_interval_seconds; bool splay; bool auto_registration; @@ -433,7 +417,7 @@ auto_register(gpointer data) } static gboolean -cert_check (gboolean heal) +cert_check (G_GNUC_UNUSED gpointer data) { int status = 0; @@ -443,23 +427,14 @@ cert_check (gboolean heal) exit (EXIT_FAILURE); } if (pid == 0) { - if (heal) { - debug ("(Auto-attach) executing: %s --autoheal", WORKER); - execl (WORKER, WORKER_NAME, "--autoheal", NULL); - } else { - debug ("(Cert check) executing: %s", WORKER); - execl (WORKER, WORKER_NAME, NULL); - } + debug ("(Cert check) executing: %s", WORKER); + execl (WORKER, WORKER_NAME, NULL); _exit (errno); } waitpid (pid, &status, 0); status = WEXITSTATUS (status); char *action = "Cert Check"; - if (heal) { - action = "Auto-attach"; - } - if (status == 0) { info ("(%s) Certificates updated.", action); } else { @@ -474,11 +449,11 @@ static gboolean initial_cert_check (gpointer data) { struct CertCheckData *cert_data = data; - cert_check (cert_data->heal); + cert_check (NULL); // Add the timeout to begin waiting on interval but offset by the initial // delay. g_timeout_add (cert_data->interval_seconds * 1000, - (GSourceFunc) cert_check, (gpointer) cert_data->heal); + (GSourceFunc) cert_check, NULL); g_timeout_add (cert_data->interval_seconds * 1000, (GSourceFunc) log_update_from_cert_data, (gpointer) cert_data); @@ -615,17 +590,6 @@ key_file_init_config (Config * config, GKeyFile * key_file) config->cert_interval_seconds = cert_frequency * 60; } - int heal_frequency = get_int_from_config_file (key_file, "rhsmcertd", - "healFrequency"); - int auto_attach_interval = get_int_from_config_file (key_file, "rhsmcertd", - "autoAttachInterval"); - if (auto_attach_interval > 0) { - config->heal_interval_seconds = auto_attach_interval * 60; - } - else if (heal_frequency > 0) { - config->heal_interval_seconds = heal_frequency * 60; - } - bool splay_enabled = get_bool_from_config_file (key_file, "rhsmcertd", "splay", DEFAULT_SPLAY_ENABLED); config->splay = splay_enabled; @@ -682,7 +646,7 @@ key_file_init_config (Config * config, GKeyFile * key_file) void deprecated_arg_init_config (Config * config, int argc, char *argv[]) { - if (argc != 3) { + if (argc != 2) { error ("Wrong number of arguments specified."); print_argument_error(N_("Wrong number of arguments specified.\n")); free (config); @@ -690,7 +654,6 @@ deprecated_arg_init_config (Config * config, int argc, char *argv[]) } config->cert_interval_seconds = atoi (argv[1]) * 60; - config->heal_interval_seconds = atoi (argv[2]) * 60; } bool @@ -701,10 +664,6 @@ opt_parse_init_config (Config * config) config->cert_interval_seconds = arg_cert_interval_minutes * 60; } - if (arg_heal_interval_minutes != -1) { - config->heal_interval_seconds = arg_heal_interval_minutes * 60; - } - if (arg_reg_interval_minutes != -1) { config->auto_reg_interval_seconds = arg_reg_interval_minutes * 60; } @@ -719,7 +678,6 @@ opt_parse_init_config (Config * config) // Let the caller know if opt parser found arg values // for the intervals. return arg_cert_interval_minutes != -1 - || arg_heal_interval_minutes != -1 || arg_reg_interval_minutes != -1 || arg_no_splay != FALSE || arg_auto_registration != FALSE; @@ -734,7 +692,6 @@ get_config (int argc, char *argv[]) // Set the default values config->auto_reg_interval_seconds = DEFAULT_AUTO_REG_INTERVAL_SECONDS; config->cert_interval_seconds = DEFAULT_CERT_INTERVAL_SECONDS; - config->heal_interval_seconds = DEFAULT_HEAL_INTERVAL_SECONDS; config->splay = DEFAULT_SPLAY_ENABLED; config->auto_registration = DEFAULT_AUTO_REGISTRATION; @@ -823,7 +780,6 @@ main (int argc, char *argv[]) // up its resources more reliably in case of error. int auto_reg_interval_seconds = config->auto_reg_interval_seconds; int cert_interval_seconds = config->cert_interval_seconds; - int heal_interval_seconds = config->heal_interval_seconds; bool splay_enabled = config->splay; bool auto_reg_enabled = config->auto_registration; free (config); @@ -861,8 +817,6 @@ main (int argc, char *argv[]) } else { debug ("Auto-registration disabled"); } - info ("Auto-attach interval: %.1f minutes [%d seconds]", - heal_interval_seconds / 60.0, heal_interval_seconds); info ("Cert check interval: %.1f minutes [%d seconds]", cert_interval_seconds / 60.0, cert_interval_seconds); @@ -873,12 +827,10 @@ main (int argc, char *argv[]) // NOTE: We put the initial checks on a timer so that in the case of systemd, // we can ensure that the network interfaces are all up before the initial // checks are done. - int auto_attach_initial_delay = 0; int cert_check_initial_delay = 0; if (run_now) { info ("Initial checks will be run now!"); } else { - int auto_attach_offset = 0; int cert_check_offset = 0; if (splay_enabled == true) { unsigned long int seed; @@ -916,13 +868,9 @@ main (int argc, char *argv[]) } #endif srand((unsigned int) seed); - auto_attach_offset = gen_random(heal_interval_seconds); cert_check_offset = gen_random(cert_interval_seconds); } - auto_attach_initial_delay = INITIAL_DELAY_SECONDS + auto_attach_offset; - info ("Waiting %.1f minutes plus %d splay seconds [%d seconds total] before performing first auto-attach.", - INITIAL_DELAY_SECONDS / 60.0, auto_attach_offset, auto_attach_initial_delay); cert_check_initial_delay = INITIAL_DELAY_SECONDS + cert_check_offset; info ("Waiting %.1f minutes plus %d splay seconds [%d seconds total] before performing first cert check.", INITIAL_DELAY_SECONDS / 60.0, cert_check_offset, cert_check_initial_delay); @@ -930,35 +878,24 @@ main (int argc, char *argv[]) struct CertCheckData auto_register_data; auto_register_data.interval_seconds = auto_reg_interval_seconds; - auto_register_data.heal = false; auto_register_data.next_update_file = NEXT_AUTO_REGISTER_UPDATE_FILE; struct CertCheckData cert_check_data; cert_check_data.interval_seconds = cert_interval_seconds; - cert_check_data.heal = false; cert_check_data.next_update_file = NEXT_CERT_UPDATE_FILE; - struct CertCheckData auto_attach_data; - auto_attach_data.interval_seconds = heal_interval_seconds; - auto_attach_data.heal = true; - auto_attach_data.next_update_file = NEXT_AUTO_ATTACH_UPDATE_FILE; - if (auto_reg_enabled) { auto_register((gpointer) &auto_register_data); } g_timeout_add (cert_check_initial_delay * 1000, (GSourceFunc) initial_cert_check, (gpointer) &cert_check_data); - g_timeout_add (auto_attach_initial_delay * 1000, - (GSourceFunc) initial_cert_check, (gpointer) &auto_attach_data); // NB: we only use cert_interval_seconds when calculating the next update - // time. This works for most users, since the cert_interval aligns with - // runs of heal_interval (i.e., heal_interval % cert_interval = 0) + // time. if (auto_reg_enabled) { log_update (0, NEXT_AUTO_REGISTER_UPDATE_FILE); } log_update (cert_check_initial_delay, NEXT_CERT_UPDATE_FILE); - log_update (auto_attach_initial_delay, NEXT_AUTO_ATTACH_UPDATE_FILE); GMainLoop *main_loop = g_main_loop_new (main_context, FALSE); g_main_loop_run (main_loop); diff --git a/src/plugins/dnf/subscription_manager.py b/src/plugins/dnf/subscription_manager.py index dffd76cca6..d8883e172c 100644 --- a/src/plugins/dnf/subscription_manager.py +++ b/src/plugins/dnf/subscription_manager.py @@ -15,17 +15,19 @@ import os import logging import shutil +import signal from subscription_manager import injection as inj -from subscription_manager.action_client import ProfileActionClient from subscription_manager.repolib import RepoActionInvoker from subscription_manager.entcertlib import EntCertActionInvoker from rhsmlib.facts.hwprobe import ClassicCheck -from subscription_manager.utils import chroot, is_simple_content_access +from subscription_manager.utils import chroot, is_simple_content_access, is_process_running from subscription_manager.injectioninit import init_dep_injection from subscription_manager.i18n import ungettext, ugettext as _ from rhsm import logutil from rhsm import config +from rhsm.utils import LiveStatusMessage +from rhsm.connection import RemoteServerException from dnfpluginscore import logger import dnf @@ -152,6 +154,7 @@ def _update(cache_only): Update entitlement certificates and redhat.repo :param cache_only: is True, when rhsm.full_refresh_on_yum is set to 0 in rhsm.conf """ + logger.info(_("Updating Subscription Management repositories.")) identity: Identity = inj.require(inj.IDENTITY) ent_dir: EntitlementDirectory = inj.require(inj.ENT_DIR) @@ -241,7 +244,48 @@ def transaction(self): cfg = config.get_config_parser() if "1" == cfg.get("rhsm", "package_profile_on_trans"): log.debug("Uploading package profile") - package_profile_client = ProfileActionClient() - package_profile_client.update() + self._upload_profile() else: log.debug("Uploading package profile disabled in configuration file") + + def _upload_profile_blocking(self) -> None: + """ + Try to upload DNF profile to server + """ + log.debug("Uploading DNF profile in blocking mode") + with LiveStatusMessage("Uploading DNF profile"): + try: + profile_mgr = inj.require(inj.PROFILE_MANAGER) + identity = inj.require(inj.IDENTITY) + profile_mgr.update_check(self.cp, identity.uuid) + except RemoteServerException as err: + # When it is not possible to upload profile ATM, then print only error about this + # to rhsm.log. The rhsmcertd will try to upload it next time. + log.error("Unable to upload profile: {err}".format(err=str(err))) + + def _upload_profile(self) -> None: + """ + Try to upload DNF profile to server, when it is supported by server. This method + tries to "outsource" this activity to rhsmcertd first. When it is not possible due to + various reasons, then we try to do it ourselves in blocking way. + """ + # First try to get PID of rhsmcertd from lock file + try: + with open("/var/lock/subsys/rhsmcertd", "r") as lock_file: + rhsmcertd_pid = int(lock_file.readline()) + except (IOError, ValueError) as err: + log.info(f"Unable to read rhsmcertd lock file: {err}") + else: + if is_process_running("rhsmcertd", rhsmcertd_pid) is True: + # This will only send SIGUSR1 signal, which triggers gathering and uploading + # of DNF profile by rhsmcertd. We try to "outsource" this activity to rhsmcertd + # server to not block registration process + log.debug(f"Sending SIGUSR1 signal to rhsmcertd process ({rhsmcertd_pid})") + try: + os.kill(rhsmcertd_pid, signal.SIGUSR1) + except OSError as err: + log.debug(f"Unable to send SIGUSR1 signal to rhsmcertd process: {err}") + self._upload_profile_blocking() + else: + log.info(f"rhsmcertd process with given PID: {rhsmcertd_pid} is not running") + self._upload_profile_blocking() diff --git a/src/plugins/libdnf/README.md b/src/plugins/libdnf/README.md index a9ed69817e..a3ae69e11d 100644 --- a/src/plugins/libdnf/README.md +++ b/src/plugins/libdnf/README.md @@ -115,12 +115,6 @@ To use testing repository you have to do several steps: subscription-manager register --username admin --password admin --org admin ``` -* Attach some subscription: - - ``` - subscription-manager attach --pool - ``` - * Choose some repository from output of `subscription-manager repos --list` and enable the repository: diff --git a/src/plugins/zypper/services/rhsm b/src/plugins/zypper/services/rhsm index 6b43660fe8..1f5b665336 100755 --- a/src/plugins/zypper/services/rhsm +++ b/src/plugins/zypper/services/rhsm @@ -19,13 +19,13 @@ import os import sys from subscription_manager import injection as inj -from subscription_manager.repofile import ZypperRepoFile from subscription_manager.repolib import RepoActionInvoker from subscription_manager.entcertlib import EntCertActionInvoker from subscription_manager.certlib import Locker from subscription_manager.injectioninit import init_dep_injection from rhsm import connection, logutil from rhsm import config +from rhsm.repofile import ZypperRepoFile from configparser import ConfigParser diff --git a/src/rhsm/config.py b/src/rhsm/config.py index 18d1ab8004..4063390d92 100644 --- a/src/rhsm/config.py +++ b/src/rhsm/config.py @@ -82,7 +82,6 @@ RHSMCERTD_DEFAULTS = { "certcheckinterval": "240", - "autoattachinterval": "1440", "splay": "1", "disable": "0", "auto_registration": "0", diff --git a/src/rhsm/connection.py b/src/rhsm/connection.py index 0dc6afcf30..bf86297c56 100644 --- a/src/rhsm/connection.py +++ b/src/rhsm/connection.py @@ -500,10 +500,14 @@ class RateLimitExceededException(RestlibException): The retry_after attribute may not be included in the response. """ - def __init__(self, code: int, msg: str = None, headers: str = None) -> None: + def __init__(self, code: int, msg: str = None, headers: dict = None) -> None: super(RateLimitExceededException, self).__init__(code, msg) self.headers = headers or {} - self.retry_after = safe_int(self.headers.get("retry-after")) + self.retry_after = None + for header, value in self.headers.items(): + if header.lower() == "retry-after": + self.retry_after = safe_int(value) + break self.msg = msg or "Access rate limit exceeded" if self.retry_after is not None: self.msg += ", retry access after: %s seconds." % self.retry_after @@ -1515,10 +1519,11 @@ def getCloudJWT(self, cloud_id: str, metadata: str, signature: str) -> Dict[str, } headers = { "Content-Type": "application/json", + "Accept": "text/plain", } return self.conn.request_post( - method="/cloud/authorize?version=2", + method="/cloud/authorize", params=data, headers=headers, description=_("Fetching cloud token"), @@ -1531,6 +1536,7 @@ def registerConsumer( facts: Optional[dict] = None, owner: str = None, environments: str = None, + environment_names: str = None, keys: str = None, installed_products: list = None, uuid: str = None, @@ -1576,6 +1582,11 @@ def registerConsumer( for environment in environments.split(","): env_list.append({"id": environment}) params["environments"] = env_list + elif environment_names is not None and self.has_capability(MULTI_ENV): + env_name_list = [] + for env_name in environment_names.split(","): + env_name_list.append({"name": env_name}) + params["environments"] = env_name_list headers = {} if jwt_token: @@ -1675,7 +1686,6 @@ def updateConsumer( guest_uuids: Union[List[str], List[dict]] = None, service_level: str = None, release: str = None, - autoheal: bool = None, hypervisor_id: str = None, content_tags: set = None, role: str = None, @@ -1706,8 +1716,6 @@ def updateConsumer( params["facts"] = facts if release is not None: params["releaseVer"] = release - if autoheal is not None: - params["autoheal"] = autoheal if hypervisor_id is not None: params["hypervisorId"] = {"hypervisorId": hypervisor_id} if content_tags is not None: @@ -1901,63 +1909,6 @@ def getAccessibleContent(self, consumerId: str, if_modified_since: datetime.date method, headers=headers, description=_("Fetching content for a certificate") ) - def bindByEntitlementPool(self, consumerId: str, poolId: str, quantity: int = None) -> List[dict]: - """ - Subscribe consumer to a subscription by pool ID - :param consumerId: consumer UUID - :param poolId: pool ID - :param quantity: the desired quantity of subscription to be consumed - """ - method = "/consumers/%s/entitlements?pool=%s" % (self.sanitize(consumerId), self.sanitize(poolId)) - if quantity: - method = "%s&quantity=%s" % (method, quantity) - return self.conn.request_post(method, description=_("Updating subscriptions")) - - def bind(self, consumerId: str, entitle_date: datetime.datetime = None) -> List[dict]: - """ - Same as bindByProduct, but assume the server has a list of the - system's products. This is useful for autosubscribe. Note that this is - done on a best-effort basis, and there are cases when the server will - not be able to fulfill the client's product certs with entitlements - :param consumerId: consumer UUID - :param entitle_date: The date, when subscription will be valid - """ - method = "/consumers/%s/entitlements" % (self.sanitize(consumerId)) - - # add the optional date to the url - if entitle_date: - method = "%s?entitle_date=%s" % (method, self.sanitize(entitle_date.isoformat(), plus=True)) - - return self.conn.request_post(method, description=_("Updating subscriptions")) - - def unbindBySerial(self, consumerId: str, serial: str) -> bool: - """ - Try to remove consumed pool by serial number - :param consumerId: consumer UUID - :param serial: serial number of consumed pool - """ - method = "/consumers/%s/certificates/%s" % (self.sanitize(consumerId), self.sanitize(str(serial))) - return self.conn.request_delete(method, description=_("Unsubscribing")) is None - - def unbindByPoolId(self, consumer_uuid: str, pool_id: str) -> bool: - """ - Try to remove consumed pool by pool ID - :param consumer_uuid: consumer UUID - :param pool_id: pool ID - :return: None - """ - method = "/consumers/%s/entitlements/pool/%s" % (self.sanitize(consumer_uuid), self.sanitize(pool_id)) - return self.conn.request_delete(method, description=_("Unsubscribing")) is None - - def unbindAll(self, consumerId: str) -> dict: - """ - Try to remove all consumed pools - :param consumerId: consumer UUID - :return: Dictionary containing statistics about removed pools - """ - method = "/consumers/%s/entitlements" % self.sanitize(consumerId) - return self.conn.request_delete(method, description=_("Unsubscribing")) - def getPoolsList( self, consumer: str = None, @@ -2057,14 +2008,17 @@ def getServiceLevelList(self, owner_key: str) -> List[str]: results = self.conn.request_get(method, description=_("Fetching service levels")) return results - def getEnvironmentList(self, owner_key: str) -> List[dict]: + def getEnvironmentList(self, owner_key: str, list_all: bool = False) -> List[dict]: """ List the environments for a particular owner. Some servers may not support this and will error out. The caller can always check with supports_resource("environments"). """ - method = "/owners/%s/environments" % self.sanitize(owner_key) + if list_all and self.has_capability("typed_environments"): + method = "/owners/%s/environments?list_all=%r" % (self.sanitize(owner_key), list_all) + else: + method = "/owners/%s/environments" % (self.sanitize(owner_key)) results = self.conn.request_get(method, description=_("Fetching environments")) return results @@ -2147,22 +2101,6 @@ def deleteContentOverrides(self, consumerId: str, params: List[dict] = None) -> params = [] return self.conn.request_delete(method, params, description=_("Removing content overrides")) - def activateMachine(self, consumerId: str, email: str, lang: str = None) -> Union[dict, None]: - """ - Activate a subscription by machine, information is located in the consumer facts - :param consumerId: consumer UUID - :param email: The email for sending notification. The notification will be sent by candlepin server - :param lang: The locale specifies the language of notification email - :return When activation was successful, then dictionary is returned. Otherwise, None is returned. - """ - method = "/subscriptions?consumer_uuid=%s" % consumerId - method += "&email=%s" % self.sanitize(email) - if (not lang) and (locale.getdefaultlocale()[0] is not None): - lang = locale.getdefaultlocale()[0].lower().replace("_", "-") - if lang: - method += "&email_locale=%s" % self.sanitize(lang) - return self.conn.request_post(method, description=_("Activating")) - # used by virt-who def getJob(self, job_id: str) -> str: """ diff --git a/src/rhsm/logutil.py b/src/rhsm/logutil.py index 486d0937f4..36e64fd1f3 100644 --- a/src/rhsm/logutil.py +++ b/src/rhsm/logutil.py @@ -10,6 +10,7 @@ from typing import Optional, Tuple, Union, List +import datetime import logging import logging.handlers import logging.config @@ -17,6 +18,7 @@ import sys import rhsm.config + LOGFILE_DIR = "/var/log/rhsm/" LOGFILE_PATH = os.path.join(LOGFILE_DIR, "rhsm.log") USER_LOGFILE_DIR = os.path.join( @@ -145,6 +147,16 @@ def __init__(self, name) -> None: self.addFilter(PyWarningsLoggingFilter(name="py.warnings")) +class RhsmISO8601Formatter(logging.Formatter): + """Ensure date & time to be in ISO8601 format and containing info about milliseconds and time zone""" + + def __init__(self): + super().__init__(fmt=LOG_FORMAT) + + def formatTime(self, record, datefmt=None): + return datetime.datetime.fromtimestamp(record.created).astimezone().isoformat(timespec="milliseconds") + + def _get_default_rhsm_log_handler() -> ( Tuple[Union[logging.handlers.RotatingFileHandler, logging.StreamHandler], Optional[str]] ): @@ -152,7 +164,7 @@ def _get_default_rhsm_log_handler() -> ( error: Optional[Exception] = None if not _rhsm_log_handler: _rhsm_log_handler, error = RHSMLogHandler(LOGFILE_PATH, USER_LOGFILE_PATH) - _rhsm_log_handler.setFormatter(logging.Formatter(LOG_FORMAT)) + _rhsm_log_handler.setFormatter(RhsmISO8601Formatter()) return _rhsm_log_handler, error @@ -160,7 +172,7 @@ def _get_default_subman_debug_handler() -> Union[None, "SubmanDebugHandler"]: global _subman_debug_handler if not _subman_debug_handler: _subman_debug_handler = SubmanDebugHandler() - _subman_debug_handler.setFormatter(logging.Formatter(LOG_FORMAT)) + _subman_debug_handler.setFormatter(RhsmISO8601Formatter()) return _subman_debug_handler diff --git a/src/rhsm/profile.py b/src/rhsm/profile.py index 374df83b52..2ba2fe7bb9 100644 --- a/src/rhsm/profile.py +++ b/src/rhsm/profile.py @@ -14,31 +14,37 @@ import importlib.util import rpm -import os.path +import os from typing import List, Union from rhsm import ourjson as json from rhsm.utils import suppress_output -from iniparse import SafeConfigParser, ConfigParser +from rhsm.repofile import get_repo_file_classes from cloud_what import provider try: - import dnf + from rhsm.repofile import dnf except ImportError: dnf = None try: - import libdnf + from rhsm.repofile import libdnf except ImportError: libdnf = None try: - import yum + from rhsm.repofile import yum except ImportError: yum = None +try: + from rhsm.repofile import apt +except ImportError: + apt = None + use_zypper: bool = importlib.util.find_spec("zypp_plugin") is not None + if use_zypper: REPOSITORY_PATH = "/etc/rhsm/zypper.repos.d/redhat.repo" else: @@ -86,7 +92,7 @@ def _uniquify(module_list: list) -> list: return list(ret.values()) @staticmethod - def fix_aws_rhui_repos(base: "dnf.Base") -> None: + def fix_aws_rhui_repos(base) -> None: """ Try to fix RHUI repos on AWS systems. When the system is running on AWS, then we have to fix repository URL. See: https://bugzilla.redhat.com/show_bug.cgi?id=1924126 @@ -191,111 +197,32 @@ def collect(self) -> List[dict]: return self.content -class EnabledRepos: - def __generate(self) -> List[dict]: - if not os.path.exists(self.repofile): - return [] - - # Unfortuantely, we can not use the SafeConfigParser for zypper repo - # files because the repository urls contains strings which the - # SafeConfigParser don't like. It would crash with - # ConfigParser.InterpolationSyntaxError: '%' must be followed by '%' or '(' - if use_zypper: - config = ConfigParser() - else: - config = SafeConfigParser() - config.read(self.repofile) - enabled_sections = [section for section in config.sections() if config.getboolean(section, "enabled")] - enabled_repos = [] - for section in enabled_sections: - try: - enabled_repos.append( - { - "repositoryid": section, - "baseurl": [self._format_baseurl(config.get(section, "baseurl"))], - } - ) - except ImportError: - break - return enabled_repos - - def __init__(self, repo_file: str) -> None: - """ - Initialize EnabledRepos - :param repo_file: A repo file path used to filter the report. - """ - if dnf is not None: - self.db = dnf.dnf.Base() - elif yum is not None: - self.yb = yum.YumBase() - - self.repofile: str = repo_file - self.content: List[dict] = self.__generate() - - def __str__(self) -> str: - return str(self.content) - - def _format_baseurl(self, repo_url: str) -> str: - """ - Returns a well formatted baseurl string - :param repo_url: a repo URL that you want to format - """ - if use_zypper: - return self._cut_question_mark(repo_url) - else: - mappings = self._obtain_mappings() - return repo_url.replace("$releasever", mappings["releasever"]).replace( - "$basearch", mappings["basearch"] - ) - - def _cut_question_mark(self, repo_url) -> str: - """ - Returns a string where everything after the first occurrence of '?' is truncated - :param repo_url: a repo URL that you want to modify - """ - return repo_url[: repo_url.find("?")] - - @suppress_output - def _obtain_mappings(self) -> dict: - """ - returns a hash with "basearch" and "releasever" set. This will try dnf first, and them yum if dnf is - not installed. - """ - if dnf is not None: - return self._obtain_mappings_dnf() - elif yum is not None: - return self._obtain_mappings_yum() - else: - log.error("Unable to load module for any supported package manager (dnf, yum).") - raise ImportError - - def _obtain_mappings_dnf(self) -> dict: - return { - "releasever": self.db.conf.substitutions["releasever"], - "basearch": self.db.conf.substitutions["basearch"], - } - - def _obtain_mappings_yum(self) -> dict: - return {"releasever": self.yb.conf.yumvar["releasever"], "basearch": self.yb.conf.yumvar["basearch"]} - - class EnabledReposProfile: """ Collect information about enabled repositories """ def __init__(self, repo_file: str = REPOSITORY_PATH) -> None: - self._enabled_repos: EnabledRepos = EnabledRepos(repo_file) + self._content = [] + + directory_path = os.path.dirname(repo_file) + file_name = os.path.basename(repo_file) + + for repo_file_cls, _ in get_repo_file_classes(): + repo = repo_file_cls(directory_path, file_name) + repo.read() + self._content.extend(repo.enabled_repos()) + self._content.sort(key=lambda x: x["baseurl"]) def __eq__(self, other: "EnabledReposProfile") -> bool: - return self._enabled_repos.content == other._enabled_repos.content + return self._content == other._content def collect(self) -> List[dict]: """ Gather list of enabled repositories :return: List of enabled repositories """ - return self._enabled_repos.content + return self._content class Package: @@ -444,7 +371,33 @@ def __eq__(self, other: "RPMProfile") -> bool: return True -def get_profile(profile_type: str) -> Union[RPMProfile, EnabledRepos, ModulesProfile]: +class DebProfile(object): + def __init__(self): + cache = apt.Cache() + self._deb_profile = [ + { + "name": package.name, + "version": package.installed.version, + "architecture": package.installed.architecture, + } + for package in cache + if package.installed is not None + ] + + def __eq__(self, other): + """ + Compare one profile to another to determine if anything has changed. + """ + if not isinstance(self, type(other)): + return False + + return self._deb_profile == other._deb_profile + + def collect(self): + return self._deb_profile + + +def get_profile(profile_type: str): """ Returns an instance of a Profile object @param profile_type: profile type @@ -458,7 +411,12 @@ def get_profile(profile_type: str) -> Union[RPMProfile, EnabledRepos, ModulesPro # Profile types we support: PROFILE_MAP: dict = { - "rpm": RPMProfile, "enabled_repos": EnabledReposProfile, - "modulemd": ModulesProfile, } + +if dnf is not None or yum is not None: + PROFILE_MAP["rpm"] = RPMProfile + PROFILE_MAP["modulemd"] = ModulesProfile + +if apt is not None: + PROFILE_MAP["deb"] = DebProfile diff --git a/src/subscription_manager/repofile.py b/src/rhsm/repofile.py similarity index 66% rename from src/subscription_manager/repofile.py rename to src/rhsm/repofile.py index c5bbee9ba4..83a36b1d51 100644 --- a/src/subscription_manager/repofile.py +++ b/src/rhsm/repofile.py @@ -23,18 +23,43 @@ import re import string import sys +from importlib import util as importlib_util try: + import apt from debian.deb822 import Deb822 +except ImportError: + apt = None + +try: + import dnf +except ImportError: + dnf = None + +try: + import libdnf +except ImportError: + libdnf = None - HAS_DEB822 = True +try: + import yum +except ImportError: + yum = None + +try: + import zypp except ImportError: - HAS_DEB822 = False + zypp = None + +if importlib_util.find_spec("zypp_plugin") is not None: + HAS_ZYPP = True +else: + HAS_ZYPP = False from subscription_manager import utils from subscription_manager.certdirectory import Path import configparser -from urllib.parse import parse_qs, urlparse, urlunparse, urlencode +from urllib.parse import parse_qs, urlparse, urlunparse, urlencode, unquote from rhsm.config import get_config_parser @@ -53,6 +78,9 @@ # detect if running with yum, otherwise it's dnf HAS_YUM = "yum" in sys.modules +# detect if running with apt +HAS_DEB822 = apt is not None + class Repo(dict): # (name, mutable, default) - The mutability information is only used in disconnected cases @@ -139,7 +167,6 @@ def from_ent_cert_content( repoid_vars = [part[1:] for part in repo_parts if part.startswith("$")] if HAS_YUM and repoid_vars: repo["ui_repoid_vars"] = " ".join(repoid_vars) - # If no GPG key URL is specified, turn gpgcheck off: gpg_url = content.gpg if not gpg_url: @@ -383,6 +410,9 @@ def create(self) -> None: def fix_content(self, content: str) -> str: return content + def enabled_repos(self): + raise NotImplementedError("'enabled_repos' is not implemented for this repo_file.") + @classmethod def installed(cls) -> bool: return os.path.exists(Path.abs(cls.PATH)) @@ -461,7 +491,8 @@ def sections(self): def fix_content(self, content): # Luckily apt ignores all Fields it does not recognize - baseurl = content["baseurl"] + parsed_url = urlparse(unquote(content["baseurl"])) + baseurl = parsed_url._replace(query="").geturl() url_res = re.match(r"^https?://(?P.*)$", baseurl) ent_res = re.match(r"^/etc/pki/entitlement/(?P.*).pem$", content["sslclientcert"]) if url_res and ent_res: @@ -469,11 +500,19 @@ def fix_content(self, content): entitlement = ent_res.group("entitlement") baseurl = "katello://{}@{}".format(entitlement, location) + query = parse_qs(parsed_url.query) + if "rel" in query and "comp" in query: + suites = query["rel"][0].replace(",", " ") + components = query["comp"][0].replace(",", " ") + else: + suites = "default" + components = "all" + apt_cont = content.copy() apt_cont["Types"] = "deb" apt_cont["URIs"] = baseurl - apt_cont["Suites"] = "default" - apt_cont["Components"] = "all" + apt_cont["Suites"] = suites + apt_cont["Components"] = components apt_cont["Trusted"] = "yes" if apt_cont["arches"] is None or apt_cont["arches"] == ["ALL"]: @@ -483,8 +522,53 @@ def fix_content(self, content): apt_cont["arches"] = arches_str apt_cont["Architectures"] = arches_str + # 'Signed-By': look for pulp public-key in '/etc/apt/trusted.gpg.d/' + keypath = "/etc/apt/trusted.gpg.d/" + keyfiles = [ + os.path.join(keypath, f) + for f in os.listdir(keypath) + if os.path.isfile(os.path.join(keypath, f)) + and (f.startswith("orcharhino_") or f.startswith("pulp_") or f.startswith("client")) + and (f.endswith(".gpg") or f.endswith(".asc")) + ] + orcharhino_keyfile = None + if len(keyfiles) > 1: + orcharhino_keyfile = keyfiles[0] + log.info(f"Found more than one pulp signing-key file; choosing '{orcharhino_keyfile}'") + elif len(keyfiles) == 1: + orcharhino_keyfile = keyfiles[0] + else: + log.warn(f"Could not find pulp signing-key in {keypath}") + if orcharhino_keyfile: + apt_cont["Signed-By"] = orcharhino_keyfile + else: + log.warn("No pulp signing-key file found!") + return apt_cont + def enabled_repos(self): + enabled_repos = [repo for repo in self.repos822 if self._getboolean(repo, "enabled")] + return [ + {"repositoryid": repo822["id"], "baseurl": [repo822["baseurl"]]} for repo822 in enabled_repos + ] + + _boolean_states = { + "1": True, + "yes": True, + "true": True, + "on": True, + "0": False, + "no": False, + "false": False, + "off": False, + } + + def _getboolean(self, repo, option): + v = repo.get(option) + if v.lower() not in self._boolean_states: + raise ValueError("Not a boolean: %s" % v) + return self._boolean_states[v.lower()] + class YumRepoFile(RepoFileBase, ConfigParser): PATH = "etc/yum.repos.d/" @@ -560,16 +644,66 @@ def section(self, section: str) -> "Repo": if self.has_section(section): return Repo(section, self.items(section)) + def enabled_repos(self): + result = [] + try: + enabled_sections = [section for section in self.sections() if self.getboolean(section, "enabled")] + for section in enabled_sections: + result.append( + {"repositoryid": section, "baseurl": [self._replace_vars(self.get(section, "baseurl"))]} + ) + except ImportError: + pass + return result + + def _replace_vars(self, repo_url): + """ + returns a string with "$basearch" and "$releasever" replaced. -class ZypperRepoFile(YumRepoFile): - """ - Class for manipulation of repo file on systems using Zypper (SuSE, OpenSuse). - """ + :param repo_url: a repo URL that you want to replace $basearch and $releasever in. + :type path: str + """ + mappings = self._obtain_mappings() + for key, value in mappings.items(): + repo_url = repo_url.replace(key, value) + return repo_url - ZYPP_RHSM_PLUGIN_CONFIG_FILE = "/etc/rhsm/zypper.conf" - PATH = "etc/rhsm/zypper.repos.d" - NAME = "redhat.repo" - REPOFILE_HEADER = """# + def _obtain_mappings(self): + """ + returns a hash with "basearch" and "releasever" set. This will try dnf first, and then yum if dnf is + not installed. + """ + if dnf is not None: + return self._obtain_mappings_dnf() + elif yum is not None: + return self._obtain_mappings_yum() + else: + log.error("Unable to load module for any supported package manager (dnf, yum).") + raise ImportError + + def _obtain_mappings_dnf(self): + db = dnf.dnf.Base() + return { + "$releasever": db.conf.substitutions["releasever"], + "$basearch": db.conf.substitutions["basearch"], + } + + def _obtain_mappings_yum(self): + yb = yum.YumBase() + return {"$releasever": yb.conf.yumvar["releasever"], "$basearch": yb.conf.yumvar["basearch"]} + + +if HAS_ZYPP: + + class ZypperRepoFile(YumRepoFile): + """ + Class for manipulation of repo file on systems using Zypper (SuSE, OpenSuse). + """ + + ZYPP_RHSM_PLUGIN_CONFIG_FILE = "/etc/rhsm/zypper.conf" + PATH = "etc/rhsm/zypper.repos.d" + NAME = "redhat.repo" + REPOFILE_HEADER = """# # Certificate-Based Repositories # Managed by (rhsm) subscription-manager # @@ -581,120 +715,128 @@ class ZypperRepoFile(YumRepoFile): # """ - def __init__(self, path: Optional[str] = None, name: Optional[str] = None): - super(ZypperRepoFile, self).__init__(path, name) - self.gpgcheck: bool = False - self.repo_gpgcheck: bool = False - self.autorefresh: bool = False - # According to - # https://github.com/openSUSE/libzypp/blob/67f55b474d67f77c1868955da8542a7acfa70a9f/zypp/media/MediaManager.h#L394 - # the following values are valid: "yes", "no", "host", "peer" - self.gpgkey_ssl_verify: Optional[str] = None - self.repo_ssl_verify: Optional[str] = None - - def read_zypp_conf(self): - """ - Read configuration file for zypper plugin - :return: None - """ - zypp_cfg = configparser.ConfigParser() - zypp_cfg.read(self.ZYPP_RHSM_PLUGIN_CONFIG_FILE) - if zypp_cfg.has_option("rhsm-plugin", "gpgcheck"): - self.gpgcheck = zypp_cfg.getboolean("rhsm-plugin", "gpgcheck") - if zypp_cfg.has_option("rhsm-plugin", "repo_gpgcheck"): - self.repo_gpgcheck = zypp_cfg.getboolean("rhsm-plugin", "repo_gpgcheck") - if zypp_cfg.has_option("rhsm-plugin", "autorefresh"): - self.autorefresh = zypp_cfg.getboolean("rhsm-plugin", "autorefresh") - if zypp_cfg.has_option("rhsm-plugin", "gpgkey-ssl-verify"): - self.gpgkey_ssl_verify = zypp_cfg.get("rhsm-plugin", "gpgkey-ssl-verify") - if zypp_cfg.has_option("rhsm-plugin", "repo-ssl-verify"): - self.repo_ssl_verify = zypp_cfg.get("rhsm-plugin", "repo-ssl-verify") - - def fix_content(self, content: "Content") -> str: - self.read_zypp_conf() - zypper_cont = content.copy() - sslverify = zypper_cont["sslverify"] - sslcacert = zypper_cont["sslcacert"] - sslclientkey = zypper_cont["sslclientkey"] - sslclientcert = zypper_cont["sslclientcert"] - proxy = zypper_cont["proxy"] - proxy_username = zypper_cont["proxy_username"] - proxy_password = zypper_cont["proxy_password"] - - del zypper_cont["sslverify"] - del zypper_cont["sslcacert"] - del zypper_cont["sslclientkey"] - del zypper_cont["sslclientcert"] - del zypper_cont["proxy"] - del zypper_cont["proxy_username"] - del zypper_cont["proxy_password"] - # NOTE looks like metadata_expire and ui_repoid_vars are ignored by zypper - - # clean up data for zypper - if zypper_cont["gpgkey"] in ["https://", "http://"]: - del zypper_cont["gpgkey"] - - # make sure gpg key download doesn't fail because of private certs - if zypper_cont["gpgkey"] and self.gpgkey_ssl_verify: - zypper_cont["gpgkey"] += "?ssl_verify=%s" % self.gpgkey_ssl_verify - - # See BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1764265 - if self.gpgcheck is False: - zypper_cont["gpgcheck"] = "0" - - # See BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1858231 - if self.repo_gpgcheck is True: - zypper_cont["repo_gpgcheck"] = "1" - else: - zypper_cont["repo_gpgcheck"] = "0" - - # See BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1797386 - if self.autorefresh is True: - zypper_cont["autorefresh"] = "1" - else: - zypper_cont["autorefresh"] = "0" - - baseurl = zypper_cont["baseurl"] - parsed = urlparse(baseurl) - zypper_query_args: Dict[str, str] = parse_qs(parsed.query) + def __init__(self, path: Optional[str] = None, name: Optional[str] = None): + super(ZypperRepoFile, self).__init__(path, name) + self.gpgcheck: bool = False + self.repo_gpgcheck: bool = False + self.autorefresh: bool = False + # According to + # https://github.com/openSUSE/libzypp/blob/67f55b474d67f77c1868955da8542a7acfa70a9f/zypp/media/MediaManager.h#L394 + # the following values are valid: "yes", "no", "host", "peer" + self.gpgkey_ssl_verify: Optional[str] = None + self.repo_ssl_verify: Optional[str] = None + + def read_zypp_conf(self): + """ + Read configuration file for zypper plugin + :return: None + """ + zypp_cfg = configparser.ConfigParser() + zypp_cfg.read(self.ZYPP_RHSM_PLUGIN_CONFIG_FILE) + if zypp_cfg.has_option("rhsm-plugin", "gpgcheck"): + self.gpgcheck = zypp_cfg.getboolean("rhsm-plugin", "gpgcheck") + if zypp_cfg.has_option("rhsm-plugin", "repo_gpgcheck"): + self.repo_gpgcheck = zypp_cfg.getboolean("rhsm-plugin", "repo_gpgcheck") + if zypp_cfg.has_option("rhsm-plugin", "autorefresh"): + self.autorefresh = zypp_cfg.getboolean("rhsm-plugin", "autorefresh") + if zypp_cfg.has_option("rhsm-plugin", "gpgkey-ssl-verify"): + self.gpgkey_ssl_verify = zypp_cfg.get("rhsm-plugin", "gpgkey-ssl-verify") + if zypp_cfg.has_option("rhsm-plugin", "repo-ssl-verify"): + self.repo_ssl_verify = zypp_cfg.get("rhsm-plugin", "repo-ssl-verify") + + def fix_content(self, content: "Content") -> str: + self.read_zypp_conf() + zypper_cont = content.copy() + sslverify = zypper_cont["sslverify"] + sslcacert = zypper_cont["sslcacert"] + sslclientkey = zypper_cont["sslclientkey"] + sslclientcert = zypper_cont["sslclientcert"] + proxy = zypper_cont["proxy"] + proxy_username = zypper_cont["proxy_username"] + proxy_password = zypper_cont["proxy_password"] + + del zypper_cont["sslverify"] + del zypper_cont["sslcacert"] + del zypper_cont["sslclientkey"] + del zypper_cont["sslclientcert"] + del zypper_cont["proxy"] + del zypper_cont["proxy_username"] + del zypper_cont["proxy_password"] + # NOTE looks like metadata_expire and ui_repoid_vars are ignored by zypper + + # clean up data for zypper + if zypper_cont["gpgkey"] in ["https://", "http://"]: + del zypper_cont["gpgkey"] + + # make sure gpg key download doesn't fail because of private certs + if zypper_cont["gpgkey"] and self.gpgkey_ssl_verify: + zypper_cont["gpgkey"] += "?ssl_verify=%s" % self.gpgkey_ssl_verify + + # See BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1764265 + if self.gpgcheck is False: + zypper_cont["gpgcheck"] = "0" + + # See BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1858231 + if self.repo_gpgcheck is True: + zypper_cont["repo_gpgcheck"] = "1" + else: + zypper_cont["repo_gpgcheck"] = "0" - if sslverify and sslverify in ["1"]: - if self.repo_ssl_verify: - zypper_query_args["ssl_verify"] = self.repo_ssl_verify + # See BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1797386 + if self.autorefresh is True: + zypper_cont["autorefresh"] = "1" else: - zypper_query_args["ssl_verify"] = "host" - - if sslcacert: - zypper_query_args["ssl_capath"] = os.path.dirname(sslcacert) - if sslclientkey: - zypper_query_args["ssl_clientkey"] = sslclientkey - if sslclientcert: - zypper_query_args["ssl_clientcert"] = sslclientcert - if proxy: - zypper_query_args["proxy"] = proxy - if proxy_username: - zypper_query_args["proxyuser"] = proxy_username - if proxy_password: - zypper_query_args["proxypass"] = proxy_password - zypper_query = urlencode(zypper_query_args) - - new_url = urlunparse( - (parsed.scheme, parsed.netloc, parsed.path, parsed.params, zypper_query, parsed.fragment) - ) - zypper_cont["baseurl"] = new_url - - return zypper_cont - - # We need to overwrite this, to avoid name clashes with yum's server_val_repo_file - @classmethod - def server_value_repo_file(cls) -> "ZypperRepoFile": - return cls("var/lib/rhsm/repo_server_val/", "zypper_{}".format(cls.NAME)) + zypper_cont["autorefresh"] = "0" + + baseurl = zypper_cont["baseurl"] + parsed = urlparse(baseurl) + zypper_query_args: Dict[str, str] = parse_qs(parsed.query) + + if sslverify and sslverify in ["1"]: + if self.repo_ssl_verify: + zypper_query_args["ssl_verify"] = self.repo_ssl_verify + else: + zypper_query_args["ssl_verify"] = "host" + + if sslcacert: + zypper_query_args["ssl_capath"] = os.path.dirname(sslcacert) + if sslclientkey: + zypper_query_args["ssl_clientkey"] = sslclientkey + if sslclientcert: + zypper_query_args["ssl_clientcert"] = sslclientcert + if proxy: + zypper_query_args["proxy"] = proxy + if proxy_username: + zypper_query_args["proxyuser"] = proxy_username + if proxy_password: + zypper_query_args["proxypass"] = proxy_password + zypper_query = urlencode(zypper_query_args) + + new_url = urlunparse( + (parsed.scheme, parsed.netloc, parsed.path, parsed.params, zypper_query, parsed.fragment) + ) + zypper_cont["baseurl"] = new_url + + return zypper_cont + + # We need to overwrite this, to avoid name clashes with yum's server_val_repo_file + @classmethod + def server_value_repo_file(cls) -> "ZypperRepoFile": + return cls("var/lib/rhsm/repo_server_val/", "zypper_{}".format(cls.NAME)) + + def _obtain_mappings(self): + db = zypp.ZConfig.instance() + return {"$basearch": str(db.systemArchitecture())} def init_repo_file_classes() -> List[Tuple[type(RepoFileBase), str]]: - repo_file_classes: List[type(RepoFileBase)] = [YumRepoFile, ZypperRepoFile] + repo_file_classes: List[type(RepoFileBase)] = [] if HAS_DEB822: repo_file_classes.append(AptRepoFile) + elif HAS_ZYPP: + repo_file_classes.append(ZypperRepoFile) + else: + repo_file_classes.append(YumRepoFile) _repo_files: List[Tuple[type(RepoFileBase), type(RepoFileBase)]] = [ (RepoFile, RepoFile.server_value_repo_file) for RepoFile in repo_file_classes if RepoFile.installed() ] diff --git a/src/rhsmlib/client_info.py b/src/rhsmlib/client_info.py index aed83ffbd5..7dbe113153 100644 --- a/src/rhsmlib/client_info.py +++ b/src/rhsmlib/client_info.py @@ -14,7 +14,7 @@ """ This module holds information about current state of client application like sender of D-bus method, current subscription-manager command (register, -attach, ...), dnf command, etc. +status...), dnf command, etc. """ import logging diff --git a/src/rhsmlib/dbus/constants.py b/src/rhsmlib/dbus/constants.py index 6215467dbc..98ddc33a7b 100644 --- a/src/rhsmlib/dbus/constants.py +++ b/src/rhsmlib/dbus/constants.py @@ -24,8 +24,6 @@ "UNREGISTER_DBUS_PATH", "CONFIG_INTERFACE", "CONFIG_DBUS_PATH", - "ATTACH_INTERFACE", - "ATTACH_DBUS_PATH", "PRODUCTS_INTERFACE", "PRODUCTS_DBUS_PATH", "ENTITLEMENT_INTERFACE", @@ -69,9 +67,6 @@ CONFIG_INTERFACE = "%s.%s" % (INTERFACE_BASE, "Config") CONFIG_DBUS_PATH = "%s/%s" % (ROOT_DBUS_PATH, "Config") -ATTACH_INTERFACE = "%s.%s" % (INTERFACE_BASE, "Attach") -ATTACH_DBUS_PATH = "%s/%s" % (ROOT_DBUS_PATH, "Attach") - PRODUCTS_INTERFACE = "%s.%s" % (INTERFACE_BASE, "Products") PRODUCTS_DBUS_PATH = "%s/%s" % (ROOT_DBUS_PATH, "Products") diff --git a/src/rhsmlib/dbus/objects/__init__.py b/src/rhsmlib/dbus/objects/__init__.py index b1c3f9473c..77a1df4eea 100644 --- a/src/rhsmlib/dbus/objects/__init__.py +++ b/src/rhsmlib/dbus/objects/__init__.py @@ -14,7 +14,6 @@ from rhsmlib.dbus.objects.config import ConfigDBusObject # NOQA from rhsmlib.dbus.objects.main import Main # NOQA from rhsmlib.dbus.objects.register import RegisterDBusObject, DomainSocketRegisterDBusObject # NOQA -from rhsmlib.dbus.objects.attach import AttachDBusObject # NOQA from rhsmlib.dbus.objects.products import ProductsDBusObject # NOQA from rhsmlib.dbus.objects.unregister import UnregisterDBusObject # NOQA from rhsmlib.dbus.objects.entitlement import EntitlementDBusObject # NOQA diff --git a/src/rhsmlib/dbus/objects/attach.py b/src/rhsmlib/dbus/objects/attach.py deleted file mode 100644 index 6830670e8f..0000000000 --- a/src/rhsmlib/dbus/objects/attach.py +++ /dev/null @@ -1,141 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# This software is licensed to you under the GNU General Public License, -# version 2 (GPLv2). There is NO WARRANTY for this software, express or -# implied, including the implied warranties of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -# along with this software; if not, see -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -# -# Red Hat trademarks are not licensed under GPLv2. No permission is -# granted to use or replicate Red Hat trademarks that are incorporated -# in this software or its documentation. -from typing import List - -import dbus -import json -import logging - -from rhsm.connection import UEPConnection -from subscription_manager import entcertlib -from subscription_manager.i18n import Locale - -from rhsmlib.dbus import constants, base_object, util, dbus_utils -from rhsmlib.services.attach import AttachService - -from subscription_manager.injectioninit import init_dep_injection -from subscription_manager.utils import is_simple_content_access - -init_dep_injection() - -log = logging.getLogger(__name__) - - -class AttachDBusImplementation(base_object.BaseImplementation): - def auto_attach(self, service_level: str, proxy_options: dict) -> dict: - self.ensure_registered() - - uep: UEPConnection = self.build_uep(proxy_options, proxy_only=True) - - # TODO Change log.info() - # to raise dbus.DBusException('Auto-attaching is not allowed in simple content access mode') - # in next minor subscription-manager release - if is_simple_content_access(uep=uep): - log.info( - "Calling D-Bus method AutoAttach() is deprecated, when Simple Content Access mode " - "is used and it will be not be supported in the next minor release of " - "subscription-manager" - ) - - service = AttachService(uep) - try: - response: dict = service.attach_auto(service_level) - except Exception as exc: - log.exception(exc) - raise dbus.DBusException(str(exc)) - - # TODO This should probably be called only if something is actually attached - entcertlib.EntCertActionInvoker().update() - return response - - def pool_attach(self, pools: List[str], quantity: int, proxy_options: dict) -> List[dict]: - self.ensure_registered() - - if quantity < 1: - raise dbus.DBusException("Quantity must be a positive number.") - - uep: UEPConnection = self.build_uep(proxy_options, proxy_only=True) - - # TODO Change log.info() - # to raise dbus.DBusException('Auto-attaching is not allowed in simple content access mode') - # in next minor subscription-manager release - if is_simple_content_access(uep=uep): - log.info( - "Calling D-Bus method AutoAttach() is deprecated, when Simple Content Access mode " - "is used and it will be not be supported in the next minor release of " - "subscription-manager" - ) - - service = AttachService(uep) - try: - results: List[dict] = [] - for pool in pools: - response = service.attach_pool(pool, quantity) - results.append(response) - except Exception as exc: - log.exception(exc) - raise dbus.DBusException(str(exc)) - - # TODO This should probably be called only if something is actually attached - entcertlib.EntCertActionInvoker().update() - return results - - -class AttachDBusObject(base_object.BaseObject): - """ - A DBus object that interacts with subscription-manager to attach various - subscriptions. Results are either a JSON string or a list of JSON strings. - We don't return the JSON in an actual dictionary because deeply nested structures - are a nightmare in DBus land. See https://stackoverflow.com/questions/31658423/ - """ - - default_dbus_path = constants.ATTACH_DBUS_PATH - interface_name = constants.ATTACH_INTERFACE - - def __init__(self, conn=None, object_path=None, bus_name=None): - super(AttachDBusObject, self).__init__(conn=conn, object_path=object_path, bus_name=bus_name) - self.impl = AttachDBusImplementation() - - @util.dbus_service_method( - constants.ATTACH_INTERFACE, - in_signature="sa{sv}s", - out_signature="s", - ) - @util.dbus_handle_sender - @util.dbus_handle_exceptions - def AutoAttach(self, service_level, proxy_options, locale, sender=None): - service_level = dbus_utils.dbus_to_python(service_level, expected_type=str) or None - proxy_options = dbus_utils.dbus_to_python(proxy_options, expected_type=dict) - locale = dbus_utils.dbus_to_python(locale, expected_type=str) - Locale.set(locale) - - result: dict = self.impl.auto_attach(service_level, proxy_options) - return json.dumps(result) - - @util.dbus_service_method( - constants.ATTACH_INTERFACE, - in_signature="asia{sv}s", - out_signature="as", - ) - @util.dbus_handle_sender - @util.dbus_handle_exceptions - def PoolAttach(self, pools, quantity, proxy_options, locale, sender=None): - pools = dbus_utils.dbus_to_python(pools, expected_type=list) - quantity = dbus_utils.dbus_to_python(quantity, expected_type=int) - proxy_options = dbus_utils.dbus_to_python(proxy_options, expected_type=dict) - - locale = dbus_utils.dbus_to_python(locale, expected_type=str) - Locale.set(locale) - - result: List[dict] = self.impl.pool_attach(pools, quantity, proxy_options) - return [json.dumps(item) for item in result] diff --git a/src/rhsmlib/dbus/objects/entitlement.py b/src/rhsmlib/dbus/objects/entitlement.py index 07cfa62620..889862e048 100644 --- a/src/rhsmlib/dbus/objects/entitlement.py +++ b/src/rhsmlib/dbus/objects/entitlement.py @@ -11,7 +11,7 @@ # granted to use or replicate Red Hat trademarks that are incorporated # in this software or its documentation. import datetime -from typing import List, Union +from typing import Union import dbus import json @@ -65,35 +65,6 @@ def get_pools(self, options: dict, proxy_options: dict) -> dict: return pools - def remove_all_entitlements(self, proxy_options: dict) -> dict: - uep: UEPConnection = self.build_uep(proxy_options, proxy_only=True) - service = EntitlementService(uep) - result: dict = service.remove_all_entitlements() - - return result - - def remove_entitlements_by_pool_ids(self, pool_ids: List[str], proxy_options: dict) -> List[str]: - """Remove entitlements by Pool IDs - - :return: List of removed serials. - """ - uep: UEPConnection = self.build_uep(proxy_options, proxy_only=True) - service = EntitlementService(uep) - _, _, removed_serials = service.remove_entitlements_by_pool_ids(pool_ids) - - return removed_serials - - def remove_entitlements_by_serials(self, serials: List[str], proxy_options: dict) -> List[str]: - """Remove entitlements by serials. - - :return: List of removed serials. - """ - uep: UEPConnection = self.build_uep(proxy_options, proxy_only=True) - service = EntitlementService(uep) - removed_serials, _ = service.remove_entitlements_by_serials(serials) - - return removed_serials - def _parse_date(self, date_string: str) -> datetime.datetime: """ Return new datetime parsed from date. @@ -187,79 +158,6 @@ def GetPools(self, options, proxy_options, locale, sender=None): pools: dict = self.impl.get_pools(options, proxy_options) return json.dumps(pools) - @util.dbus_service_method( - constants.ENTITLEMENT_INTERFACE, - in_signature="a{sv}s", - out_signature="s", - ) - @util.dbus_handle_sender - @util.dbus_handle_exceptions - def RemoveAllEntitlements(self, proxy_options, locale, sender=None): - """ - Try to remove all entitlements (subscriptions) from the system - :param proxy_options: Settings of proxy - :param locale: String with locale (e.g. de_DE.UTF-8) - :param sender: Not used argument - :return: Json string containing response - """ - proxy_options = dbus_utils.dbus_to_python(proxy_options, expected_type=dict) - locale = dbus_utils.dbus_to_python(locale, expected_type=str) - - Locale.set(locale) - - result: dict = self.impl.remove_all_entitlements(proxy_options) - return json.dumps(result) - - @util.dbus_service_method( - constants.ENTITLEMENT_INTERFACE, - in_signature="asa{sv}s", - out_signature="s", - ) - @util.dbus_handle_sender - @util.dbus_handle_exceptions - def RemoveEntitlementsByPoolIds(self, pool_ids, proxy_options, locale, sender=None): - """ - Try to remove entitlements (subscriptions) by pool_ids - :param pool_ids: List of pool IDs - :param proxy_options: Settings of proxy - :param locale: String with locale (e.g. de_DE.UTF-8) - :param sender: Not used argument - :return: Json string representing list of serial numbers - """ - pool_ids = dbus_utils.dbus_to_python(pool_ids, expected_type=list) - proxy_options = dbus_utils.dbus_to_python(proxy_options, expected_type=dict) - locale = dbus_utils.dbus_to_python(locale, expected_type=str) - - Locale.set(locale) - - removed_serials = self.impl.remove_entitlements_by_pool_ids(pool_ids, proxy_options) - return json.dumps(removed_serials) - - @util.dbus_service_method( - constants.ENTITLEMENT_INTERFACE, - in_signature="asa{sv}s", - out_signature="s", - ) - @util.dbus_handle_sender - @util.dbus_handle_exceptions - def RemoveEntitlementsBySerials(self, serials, proxy_options, locale, sender=None): - """ - Try to remove entitlements (subscriptions) by serials - :param serials: List of serial numbers of subscriptions - :param proxy_options: Settings of proxy - :param locale: String with locale (e.g. de_DE.UTF-8) - :param sender: Not used argument - :return: Json string representing list of serial numbers - """ - serials = dbus_utils.dbus_to_python(serials, expected_type=list) - proxy_options = dbus_utils.dbus_to_python(proxy_options, expected_type=dict) - locale = dbus_utils.dbus_to_python(locale, expected_type=str) - - Locale.set(locale) - - removed_serials = self.impl.remove_entitlements_by_serials(serials, proxy_options) - return json.dumps(removed_serials) - @staticmethod def reload(): entitlement_service = EntitlementService() diff --git a/src/rhsmlib/dbus/objects/register.py b/src/rhsmlib/dbus/objects/register.py index 3b03419606..e2b68d559d 100644 --- a/src/rhsmlib/dbus/objects/register.py +++ b/src/rhsmlib/dbus/objects/register.py @@ -23,8 +23,8 @@ from rhsmlib.dbus import constants, exceptions, dbus_utils, base_object, server, util from rhsmlib.services.register import RegisterService from rhsmlib.services.unregister import UnregisterService -from rhsmlib.services.attach import AttachService from rhsmlib.services.entitlement import EntitlementService +from rhsmlib.services.environment import EnvironmentService from rhsmlib.client_info import DBusSender from subscription_manager.cp_provider import CPProvider @@ -37,6 +37,14 @@ log = logging.getLogger(__name__) +ENVIRONMENTS_KEYS_TO_FILTER: List[str] = [ + "contentPrefix", + "created", + "environmentContent", + "owner", + "updated", +] + class RegisterDBusImplementation(base_object.BaseImplementation): def __init__(self): @@ -182,6 +190,25 @@ def get_organizations(self, options: dict) -> List[dict]: owners: List[dict] = uep.getOwnerList(options["username"]) return owners + def get_environments(self, options: dict) -> List[dict]: + """Get environments for every org belonging to user + + :param options: Connection options including the 'username', 'password', and 'org_id' keys + :return: List of environments + """ + uep: UEPConnection = self.build_uep(options, basic_auth_method=True) + environment_service: EnvironmentService = EnvironmentService(uep) + + environments = environment_service.list(options["org_id"]) + + for environment in environments: + for key in ENVIRONMENTS_KEYS_TO_FILTER: + environment.pop(key, None) + if ("type" not in environment) or (environment["type"] is None): + environment["type"] = "" + + return environments + def register_with_credentials( self, organization: Optional[str], register_options: dict, connection_options: dict ) -> dict: @@ -268,22 +295,12 @@ def _remove_enable_content_option(self, options: dict) -> bool: return bool(options.pop("enable_content")) def _enable_content(self, uep: "UEPConnection", consumer: dict) -> None: - """Try to enable content: Auto-attach in non-SCA or refresh in SCA mode.""" + """Try to enable content: refresh SCA entitlement certs in SCA mode.""" content_access: str = consumer["owner"]["contentAccessMode"] enabled_content = None if content_access == "entitlement": - log.debug("Auto-attaching since 'enable_content' is true.") - service = AttachService(uep) - enabled_content = service.attach_auto() - if len(enabled_content) > 0: - log.debug("Updating entitlement certificates") - # FIXME: The enabled_content contains all data necessary for generating entitlement - # certificate and private key. Thus we could save few REST API calls, when the data was used. - EntCertActionInvoker().update() - else: - log.debug("No content was enabled, entitlement certificates not updated.") - + log.error("Entitlement content access mode is not supported") elif content_access == "org_environment": log.debug("Refreshing since 'enable_content' is true.") service = EntitlementService(uep) @@ -344,6 +361,40 @@ def __init__(self, conn=None, object_path=None, bus_name=None, sender=None, cmd_ self.sender = sender self.cmd_line = cmd_line + @dbus.service.method( + dbus_interface=constants.PRIVATE_REGISTER_INTERFACE, + in_signature="sssa{sv}s", + out_signature="s", + ) + @util.dbus_handle_exceptions + def GetEnvironments(self, username, password, org_id, connection_options, locale): + """ + This method tries to return list of environments in the given orgs. This method also uses + basic authentication method (using username and password). + + :param username: string with username used for connection to candlepin server + :param password: string with password + :param org_id: string with org id to list environments for + :param connection_options: dictionary with connection options + :param locale: string with locale + :return: string with json returned by candlepin server + """ + connection_options = dbus_utils.dbus_to_python(connection_options, expected_type=dict) + connection_options["username"] = dbus_utils.dbus_to_python(username, expected_type=str) + connection_options["password"] = dbus_utils.dbus_to_python(password, expected_type=str) + connection_options["org_id"] = dbus_utils.dbus_to_python(org_id, expected_type=str) + locale = dbus_utils.dbus_to_python(locale, expected_type=str) + + with DBusSender() as dbus_sender: + dbus_sender.set_cmd_line(sender=self.sender, cmd_line=self.cmd_line) + Locale.set(locale) + + environments = self.impl.get_environments(connection_options) + + dbus_sender.reset_cmd_line() + + return json.dumps(environments) + @dbus.service.method( dbus_interface=constants.PRIVATE_REGISTER_INTERFACE, in_signature="ssa{sv}s", @@ -409,8 +460,6 @@ def Register(self, org, username, password, options, connection_options, locale) used. Options is a dict of strings that modify the outcome of this method. - - Note this method is registration ONLY. Auto-attach is a separate process. """ org = dbus_utils.dbus_to_python(org, expected_type=str) connection_options = dbus_utils.dbus_to_python(connection_options, expected_type=dict) @@ -435,7 +484,7 @@ def Register(self, org, username, password, options, connection_options, locale) @util.dbus_handle_exceptions def RegisterWithActivationKeys(self, org, activation_keys, options, connection_options, locale): """ - Note this method is registration ONLY. Auto-attach is a separate process. + This method registers the system using organization and activation keys """ connection_options = dbus_utils.dbus_to_python(connection_options, expected_type=dict) options = dbus_utils.dbus_to_python(options, expected_type=dict) diff --git a/src/rhsmlib/facts/cloud_facts.py b/src/rhsmlib/facts/cloud_facts.py index c429412b88..1b9972cb8e 100644 --- a/src/rhsmlib/facts/cloud_facts.py +++ b/src/rhsmlib/facts/cloud_facts.py @@ -123,6 +123,8 @@ def get_azure_facts(self) -> Dict[str, str]: "azure_instance_id": some_instance_ID, "azure_offer": some_offer, "azure_sku": some_sku, + "azure_vm_name": some_vm_name + "azure_resource_group_name": some_resource_group_name "azure_subscription_id": some_subscription_ID "azure_location: azure region the VM is running in } @@ -142,6 +144,10 @@ def get_azure_facts(self) -> Dict[str, str]: facts["azure_sku"] = values["compute"]["sku"] if "offer" in values["compute"]: facts["azure_offer"] = values["compute"]["offer"] + if "name" in values["compute"]: + facts["azure_vm_name"] = values["compute"]["name"] + if "resourceGroupName" in values["compute"]: + facts["azure_resource_group_name"] = values["compute"]["resourceGroupName"] if "subscriptionId" in values["compute"]: facts["azure_subscription_id"] = values["compute"]["subscriptionId"] if "location" in values["compute"]: diff --git a/src/rhsmlib/services/attach.py b/src/rhsmlib/services/attach.py deleted file mode 100644 index c141da1400..0000000000 --- a/src/rhsmlib/services/attach.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# This software is licensed to you under the GNU General Public License, -# version 2 (GPLv2). There is NO WARRANTY for this software, express or -# implied, including the implied warranties of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -# along with this software; if not, see -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -# -# Red Hat trademarks are not licensed under GPLv2. No permission is -# granted to use or replicate Red Hat trademarks that are incorporated -# in this software or its documentation. -import logging - -from subscription_manager import injection as inj -from rhsm.connection import UEPConnection - -log = logging.getLogger(__name__) - - -class AttachService: - """ - Service using for attach pools by ID or auto-attaching - """ - - def __init__(self, cp: UEPConnection) -> None: - self.plugin_manager = inj.require(inj.PLUGIN_MANAGER) - self.identity = inj.require(inj.IDENTITY) - self.cp = cp - - def attach_auto(self, service_level: str = None) -> dict: - """ - Try to perform auto-attach. Candlepin server tries to attach pool according - installed product certificates and other attributes (service_level, syspurpose, etc.) - :param service_level: String with requested service level - :return: Dictionary with result of REST-API call - """ - - # FIXME: First check if current service_level is the same as provided in - # argument and if not then try to set something new. Otherwise it is useless - # and it only cause unnecessary REST API call - if service_level is not None: - self.cp.updateConsumer(self.identity.uuid, service_level=service_level) - log.debug("Service level set to: %s" % service_level) - - self.plugin_manager.run( - "pre_auto_attach", - consumer_uuid=self.identity.uuid, - ) - - resp = self.cp.bind(self.identity.uuid) - - self.plugin_manager.run( - "post_auto_attach", - consumer_uuid=self.identity.uuid, - entitlement_data=resp, - ) - - return resp - - def attach_pool(self, pool: str, quantity: int) -> dict: - """ - Try to attach pool - :param pool: String with pool ID - :param quantity: Quantity of subscriptions user wants to consume - :return: Dictionary with result of REST-API call - """ - - # If quantity is None, server will assume 1. pre_subscribe will - # report the same. - self.plugin_manager.run( - "pre_subscribe", consumer_uuid=self.identity.uuid, pool_id=pool, quantity=quantity - ) - - resp = self.cp.bindByEntitlementPool(self.identity.uuid, pool, quantity) - - self.plugin_manager.run( - "post_subscribe", - consumer_uuid=self.identity.uuid, - entitlement_data=resp, - ) - - return resp diff --git a/src/rhsmlib/services/entitlement.py b/src/rhsmlib/services/entitlement.py index 527cbc5f0c..d3d1d89ec5 100644 --- a/src/rhsmlib/services/entitlement.py +++ b/src/rhsmlib/services/entitlement.py @@ -15,7 +15,7 @@ import datetime import logging import time -from typing import Union, Callable +from typing import Union from subscription_manager import injection as inj from subscription_manager.i18n import ugettext as _ @@ -518,77 +518,6 @@ def validate_options(self, options: dict) -> None: elif not self.identity.is_valid() and "available" in options["pool_subsets"]: raise exceptions.ValidationError(_("Error: this system is not registered")) - def _unbind_ids(self, unbind_method: Callable, consumer_uuid: str, ids: list) -> tuple: - """ - Method for unbinding entitlements - :param unbind_method: unbindByPoolId or unbindBySerial - :param consumer_uuid: UUID of consumer - :param ids: List of serials or pool_ids - :return: Tuple of two lists containing unbinded and not-unbinded subscriptions - """ - success = [] - failure = [] - for id_ in ids: - try: - unbind_method(consumer_uuid, id_) - success.append(id_) - except connection.RestlibException as re: - if re.code == 410: - raise - failure.append(id_) - log.error(re) - return success, failure - - def remove_all_entitlements(self) -> dict: - """ - Try to remove all entitlements - :return: Result of REST API call - """ - - response = self.cp.unbindAll(self.identity.uuid) - self.entcertlib.update() - - return response - - def remove_entitlements_by_pool_ids(self, pool_ids: list) -> tuple: - """ - Try to remove entitlements by pool IDs - :param pool_ids: List of pool IDs - :return: List of serial numbers of removed subscriptions - """ - - removed_serials = [] - _pool_ids = utils.unique_list_items(pool_ids) # Don't allow duplicates - # FIXME: the cache of CertificateDirectory should be smart enough and refreshing - # should not be necessary. I vote for i-notify to be used there somehow. - self.entitlement_dir.refresh() - pool_id_to_serials = self.entitlement_dir.list_serials_for_pool_ids(_pool_ids) - removed_pools, unremoved_pools = self._unbind_ids( - self.cp.unbindByPoolId, self.identity.uuid, _pool_ids - ) - if removed_pools: - for pool_id in removed_pools: - removed_serials.extend(pool_id_to_serials[pool_id]) - self.entcertlib.update() - - return removed_pools, unremoved_pools, removed_serials - - def remove_entitlements_by_serials(self, serials: list) -> tuple: - """ - Try to remove pools by Serial numbers - :param serials: List of serial numbers - :return: Tuple of two items: list of serial numbers of already removed subscriptions and list - of not removed serials - """ - - _serials = utils.unique_list_items(serials) # Don't allow duplicates - removed_serials, unremoved_serials = self._unbind_ids( - self.cp.unbindBySerial, self.identity.uuid, _serials - ) - self.entcertlib.update() - - return removed_serials, unremoved_serials - @staticmethod def reload() -> None: """ @@ -612,11 +541,6 @@ def refresh(self, remove_cache: bool = False, force: bool = False) -> None: content_access = inj.require(inj.CONTENT_ACCESS_CACHE) if content_access.exists(): content_access.remove() - # Also remove the content access mode cache to be sure we display - # SCA or regular mode correctly - content_access_mode = inj.require(inj.CONTENT_ACCESS_MODE_CACHE) - if content_access_mode.exists(): - content_access_mode.delete_cache() if force is True: # Force a regen of the entitlement certs for this consumer diff --git a/src/rhsmlib/services/environment.py b/src/rhsmlib/services/environment.py new file mode 100644 index 0000000000..ef70163668 --- /dev/null +++ b/src/rhsmlib/services/environment.py @@ -0,0 +1,43 @@ +# Copyright (c) 2024 Red Hat, Inc. +# +# This software is licensed to you under the GNU General Public License, +# version 2 (GPLv2). There is NO WARRANTY for this software, express or +# implied, including the implied warranties of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 +# along with this software; if not, see +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +# +# Red Hat trademarks are not licensed under GPLv2. No permission is +# granted to use or replicate Red Hat trademarks that are incorporated +# in this software or its documentation. +import logging +from typing import List + + +from rhsm.connection import UEPConnection + +log = logging.getLogger(__name__) + + +class EnvironmentService: + """ + Class for using environments + """ + + def __init__(self, cp: UEPConnection) -> None: + """ + Initialization of EnvironmentService instance + :param cp: connection to Candlepin + """ + self.cp = cp + + def list(self, org_id: str) -> List[dict]: + """ + Method for listing environments + :param org_id: organization to list environments for + :return: List of environments. + """ + list_all = self.cp.has_capability("typed_environments") + environments = self.cp.getEnvironmentList(org_id, list_all=list_all) + + return environments diff --git a/src/rhsmlib/services/refresh.py b/src/rhsmlib/services/refresh.py index 6ddc2d57d3..37502a00ec 100644 --- a/src/rhsmlib/services/refresh.py +++ b/src/rhsmlib/services/refresh.py @@ -54,12 +54,6 @@ def refresh(self, force: bool = False) -> None: :return: None """ - # First remove the content access mode cache to be sure we display - # SCA or regular mode correctly - content_access_mode = inj.require(inj.CONTENT_ACCESS_MODE_CACHE) - if content_access_mode.exists(): - content_access_mode.delete_cache() - # Remove the release status cache, in case it was changed # on the server; it will be fetched when needed again inj.require(inj.RELEASE_STATUS_CACHE).delete_cache() diff --git a/src/rhsmlib/services/register.py b/src/rhsmlib/services/register.py index 76e1669088..b244b862d7 100644 --- a/src/rhsmlib/services/register.py +++ b/src/rhsmlib/services/register.py @@ -18,12 +18,18 @@ from rhsm.connection import UEPConnection from rhsmlib.services import exceptions +from rhsmlib.services.unregister import UnregisterService from subscription_manager import injection as inj from subscription_manager import managerlib from subscription_manager import syspurposelib from subscription_manager.i18n import ugettext as _ +import typing + +if typing.TYPE_CHECKING: + from subscription_manager.cp_provider import CPProvider + log = logging.getLogger(__name__) @@ -40,6 +46,8 @@ def register( org: Optional[str], activation_keys: list = None, environments: list = None, + environment_names: list = None, + environment_type: str = None, force: bool = False, name: str = None, consumerid: str = None, @@ -49,7 +57,7 @@ def register( service_level: str = None, usage: str = None, jwt_token: str = None, - **kwargs: dict + **kwargs: dict, ) -> dict: # We accept a kwargs argument so that the DBus object can pass the options dictionary it # receives transparently to the service via dictionary unpacking. This strategy allows the @@ -60,6 +68,11 @@ def register( if kwargs: raise exceptions.ValidationError(_("Unknown arguments: %s") % kwargs.keys()) + if environments is not None and environment_names is not None: + raise exceptions.ValidationError( + _("Environment IDs and environment names are mutually exclusive") + ) + syspurpose = syspurposelib.read_syspurpose() save_syspurpose = False @@ -89,6 +102,7 @@ def register( options = { "activation_keys": activation_keys, "environments": environments, + "environment_names": environment_names, "force": force, "name": name, "consumerid": consumerid, @@ -117,6 +131,7 @@ def register( facts=facts_dict, owner=org, environments=environments, + environment_names=environment_names, keys=options.get("activation_keys"), installed_products=self.installed_mgr.format_for_server(), content_tags=self.installed_mgr.tags, @@ -129,12 +144,68 @@ def register( ) # When new consumer is created, then close all existing connections # to be able to recreate new one - cp_provider = inj.require(inj.CP_PROVIDER) + cp_provider: CPProvider = inj.require(inj.CP_PROVIDER) cp_provider.close_all_connections() + # If environment type was specified, then check that all returned + # environments have required type. Otherwise, raise exception + wrong_env_names = [] + if environment_type is not None: + for environment in consumer.get("environments", []): + env_type = environment.get("type", None) + if env_type != environment_type: + environment_name = environment["name"] + log.error( + f"Environment: '{environment_name}' does not have required type: '{environment_type}," + f" it has '{env_type}' type" + ) + wrong_env_names.append(environment_name) + + managerlib.persist_consumer_cert(consumer) + + if len(wrong_env_names) > 0: + # We will not use this consumer object. Thus, delete this object + # on the server + self.identity.reload() + UnregisterService(inj.require(inj.CP_PROVIDER).get_consumer_auth_cp()).unregister() + if len(wrong_env_names) == 1: + raise exceptions.ServiceError( + _( + "Environment: '{env_names}' does not have required type '{environment_type}'".format( + env_names=wrong_env_names[0], environment_type=environment_type + ) + ) + ) + else: + raise exceptions.ServiceError( + _( + "Environments: '{env_names}' do not have required type '{environment_type}'".format( + env_names=", ".join(wrong_env_names), environment_type=environment_type + ) + ) + ) + + access_mode: str = consumer.get("owner", {}).get("contentAccessMode", "unknown") + if access_mode != "org_environment": + log.error( + f"Organization's content access mode is '{access_mode}'. " + "Only Simple Content Access ('org_environment') is allowed. " + "Unregistering." + ) + + # Use newly saved certificate to unregister the system; the presence of identity + # certificate does not mean we can get content. + self.identity.reload() + UnregisterService(inj.require(inj.CP_PROVIDER).get_consumer_auth_cp()).unregister() + raise exceptions.ServiceError( + _( + "Registration is only possible when the organization " + "is in Simple Content Access (SCA) mode." + ) + ) + self.installed_mgr.write_cache() self.plugin_manager.run("post_register_consumer", consumer=consumer, facts=facts_dict) - managerlib.persist_consumer_cert(consumer) # Now that we are registered, load the new identity self.identity.reload() @@ -159,26 +230,6 @@ def register( # Save syspurpose attributes from consumer to cache file syspurposelib.write_syspurpose_cache(syspurpose_dict) - content_access_mode_cache = inj.require(inj.CONTENT_ACCESS_MODE_CACHE) - - # Is information about content access mode included in consumer - if "owner" not in consumer: - log.warning("Consumer does not contain any information about owner.") - elif "contentAccessMode" in consumer["owner"]: - log.debug("Saving content access mode from consumer object to cache file.") - # When we know content access mode from consumer, then write it to cache file - content_access_mode = consumer["owner"]["contentAccessMode"] - content_access_mode_cache.set_data(content_access_mode, self.identity) - content_access_mode_cache.write_cache() - else: - # If not, then we have to do another REST API call to get this information - # It will not be included in cache file. When cache file is empty, then - # it will trigger accessing REST API and saving result in cache file. - log.debug("Information about content access mode is not included in consumer") - content_access_mode = content_access_mode_cache.read_data() - # Add information about content access mode to consumer - consumer["owner"]["contentAccessMode"] = content_access_mode - return consumer def validate_options(self, options: dict) -> None: @@ -213,16 +264,11 @@ def validate_options(self, options: dict) -> None: raise exceptions.ValidationError( _("Error: Activation keys can not be used with previously" " registered IDs.") ) - elif options["environments"]: - raise exceptions.ValidationError( - _("Error: Activation keys do not allow environments to be" " specified.") - ) elif options.get("jwt_token") is not None: # TODO: add more checks here pass elif not getattr(self.cp, "username", None) or not getattr(self.cp, "password", None): - if not getattr(self.cp, "token", None): - raise exceptions.ValidationError(_("Error: Missing username or password.")) + raise exceptions.ValidationError(_("Error: Missing username or password.")) def determine_owner_key(self, username: str, get_owner_cb: Callable, no_owner_cb: Callable) -> str: """ diff --git a/src/subscription_manager/action_client.py b/src/subscription_manager/action_client.py index fc2a33f834..e8b431ddba 100644 --- a/src/subscription_manager/action_client.py +++ b/src/subscription_manager/action_client.py @@ -26,7 +26,6 @@ from subscription_manager.entcertlib import EntCertActionInvoker from subscription_manager.identitycertlib import IdentityCertActionInvoker -from subscription_manager.healinglib import HealingActionInvoker from subscription_manager.factlib import FactsActionInvoker from subscription_manager.packageprofilelib import PackageProfileActionInvoker from subscription_manager.installedproductslib import InstalledProductsActionInvoker @@ -48,8 +47,7 @@ def _get_libset(self) -> List["BaseActionInvoker"]: self.syspurposelib = SyspurposeSyncActionInvoker() # WARNING: order is important here, we need to update a number - # of things before attempting to autoheal, and we need to autoheal - # before attempting to fetch our certificates: + # of things before attempting to fetch our certificates: lib_set: List[BaseActionInvoker] = [ self.entcertlib, self.idcertlib, @@ -63,23 +61,6 @@ def _get_libset(self) -> List["BaseActionInvoker"]: return lib_set -class HealingActionClient(base_action_client.BaseActionClient): - def _get_libset(self) -> List["BaseActionInvoker"]: - self.entcertlib = EntCertActionInvoker() - self.installedprodlib = InstalledProductsActionInvoker() - self.syspurposelib = SyspurposeSyncActionInvoker() - self.healinglib = HealingActionInvoker() - - lib_set: List[BaseActionInvoker] = [ - self.installedprodlib, - self.syspurposelib, - self.healinglib, - self.entcertlib, - ] - - return lib_set - - # it may make more sense to have *Lib.cleanup actions? # *Lib things are weird, since some are idempotent, but # some are not. entcertlib/repolib .update can both install diff --git a/src/subscription_manager/cache.py b/src/subscription_manager/cache.py index 5fb2c4eb47..a2d84088d5 100644 --- a/src/subscription_manager/cache.py +++ b/src/subscription_manager/cache.py @@ -37,7 +37,7 @@ from rhsm.config import get_config_parser import rhsm.connection as connection -from rhsm.profile import get_profile +from rhsm.profile import get_profile, PROFILE_MAP import subscription_manager.injection as inj from subscription_manager.jsonwrapper import PoolWrapper from rhsm import ourjson as json @@ -503,13 +503,9 @@ def _assembly_profile(rpm_profile, enabled_repos_profile, module_profile) -> Dic @property def current_profile(self) -> Dict[str, List[Dict]]: if not self._current_profile: - rpm_profile: List[Dict] = get_profile("rpm").collect() - enabled_repos: List[Dict] = get_profile("enabled_repos").collect() - module_profile: List[Dict] = get_profile("modulemd").collect() - combined_profile: Dict[str, List[Dict]] = self._assembly_profile( - rpm_profile, enabled_repos, module_profile - ) - self._current_profile = combined_profile + self._current_profile: Dict[str, List[Dict]] = { + key: get_profile(key).collect() for key in PROFILE_MAP.keys() + } return self._current_profile @current_profile.setter @@ -562,9 +558,11 @@ def _sync_with_server( combined_profile: Dict = self.current_profile if uep.has_capability("combined_reporting"): _combined_profile: List[Dict] = [ - {"content_type": "rpm", "profile": combined_profile["rpm"]}, - {"content_type": "enabled_repos", "profile": combined_profile["enabled_repos"]}, - {"content_type": "modulemd", "profile": combined_profile["modulemd"]}, + { + "content_type": key, + "profile": value, + } + for key, value in combined_profile.items() ] uep.updateCombinedProfile(consumer_uuid, _combined_profile) else: @@ -1023,72 +1021,6 @@ def _is_cache_obsoleted(self, uep: connection.UEPConnection, identity: "Identity return True -class ContentAccessModeCache(ConsumerCache): - """ - Cache information about current owner (organization), specifically, the content access mode. - This value is used independently. - """ - - # Grab the current owner (and hence the content_access_mode of that owner) at most, once per - # 4 hours - TIMEOUT = 60 * 60 * 4 - - CACHE_FILE = "/var/lib/rhsm/cache/content_access_mode.json" - - def __init__(self, data: Any = None): - super(ContentAccessModeCache, self).__init__(data=data) - - def _sync_with_server( - self, uep: connection.UEPConnection, consumer_uuid: str, _: Optional[datetime.datetime] = None - ) -> str: - try: - current_owner: Dict = uep.getOwner(consumer_uuid) - except Exception: - log.debug( - "Error checking for content access mode," - "defaulting to assuming not in Simple Content Access mode" - ) - else: - if "contentAccessMode" in current_owner: - return current_owner["contentAccessMode"] - else: - log.debug( - "The owner returned from the server did not contain a " - "'content_access_mode'. Perhaps the connected Entitlement Server doesn't" - "support 'content_access_mode'?" - ) - return "unknown" - - def _is_cache_obsoleted(self, uep: connection.UEPConnection, identity: "Identity"): - """ - We don't know if the cache is valid until we get valid response - :param uep: object representing connection to candlepin server - :param identity: consumer identity - :return: True, when cache is obsoleted or validity of cache is unknown. - """ - if uep is None: - cp_provider: CPProvider = inj.require(inj.CP_PROVIDER) - uep: connection.UEPConnection = cp_provider.get_consumer_auth_cp() - - if hasattr(uep.conn, "is_consumer_cert_key_valid"): - if uep.conn.is_consumer_cert_key_valid is None: - log.debug( - f"Cache file {self.CACHE_FILE} cannot be considered as valid, because no connection has " - "been created yet" - ) - return True - elif uep.conn.is_consumer_cert_key_valid is True: - return False - else: - log.debug( - f"Cache file {self.CACHE_FILE} cannot be considered as valid, " - "because consumer certificate probably is not valid" - ) - return True - else: - return True - - class SupportedResourcesCache(ConsumerCache): """ Cache supported resources of candlepin server for current identity diff --git a/src/subscription_manager/certdirectory.py b/src/subscription_manager/certdirectory.py index 891d4d758a..c3138d44f1 100644 --- a/src/subscription_manager/certdirectory.py +++ b/src/subscription_manager/certdirectory.py @@ -336,15 +336,6 @@ def list_for_pool_id(self, pool_id: str) -> List["EntitlementCertificate"]: ] return entitlements - def list_serials_for_pool_ids(self, pool_ids: List[str]) -> Dict[str, List[str]]: - """ - Returns a dict of all entitlement certificate serials for each pool_id in the list provided - """ - pool_id_to_serials = {} - for pool_id in pool_ids: - pool_id_to_serials[pool_id] = [str(cert.serial) for cert in self.list_for_pool_id(pool_id)] - return pool_id_to_serials - class Path: # Used during Anaconda install by the yum pidplugin to ensure we operate diff --git a/src/subscription_manager/cli_command/abstract_syspurpose.py b/src/subscription_manager/cli_command/abstract_syspurpose.py index cc9151ceae..9dbf5f989e 100644 --- a/src/subscription_manager/cli_command/abstract_syspurpose.py +++ b/src/subscription_manager/cli_command/abstract_syspurpose.py @@ -145,11 +145,7 @@ def _validate_options(self): if not self.is_registered(): if self.options.list: - if self.options.token and not self.options.username: - pass - elif self.options.token and self.options.username: - system_exit(os.EX_USAGE, _("Error: you can specify --username or --token not both")) - elif not self.options.username or not self.options.password: + if not self.options.username or not self.options.password: system_exit( os.EX_USAGE, _( @@ -164,15 +160,11 @@ def _validate_options(self): if self.is_registered() and ( getattr(self.options, "username", None) or getattr(self.options, "password", None) - or getattr(self.options, "token", None) or getattr(self.options, "org", None) ): system_exit( os.EX_USAGE, - _( - "Error: --username, --password, --token and --org " - "can be used only on unregistered systems" - ), + _("Error: --username, --password, and --org " "can be used only on unregistered systems"), ) def _get_valid_fields(self): @@ -241,11 +233,7 @@ def _are_provided_values_valid(self, values): # When the system is not registered and no username & password was provided, then # these values will be set silently. if invalid_values_len > 0: - if ( - self.is_registered() - or (self.options.username and self.options.password) - or self.options.token - ): + if self.is_registered() or (self.options.username and self.options.password): if len(valid_fields.get(self.attr, [])) > 0: # TRANSLATORS: this is used to quote a string quoted_values = [_('"{value}"').format(value=value) for value in invalid_values] @@ -423,17 +411,7 @@ def _do_command(self): # If we have a username/password, we're going to use that, otherwise # we'll use the identity certificate. We already know one or the other # exists: - if self.options.token: - try: - self.cp = self.cp_provider.get_keycloak_auth_cp(self.options.token) - except Exception as err: - log.error( - 'unable to connect to candlepin server using token: "{token}", err: {err}'.format( - token=self.options.token, err=err - ) - ) - print(_("Unable to connect to server using token")) - elif self.options.username and self.options.password: + if self.options.username and self.options.password: self.cp_provider.set_user_pass(self.options.username, self.options.password) self.cp = self.cp_provider.get_basic_auth_cp() else: diff --git a/src/subscription_manager/cli_command/addons.py b/src/subscription_manager/cli_command/addons.py deleted file mode 100644 index 16a96f3ac5..0000000000 --- a/src/subscription_manager/cli_command/addons.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# Subscription manager command line utility. -# -# Copyright (c) 2021 Red Hat, Inc. -# -# This software is licensed to you under the GNU General Public License, -# version 2 (GPLv2). There is NO WARRANTY for this software, express or -# implied, including the implied warranties of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -# along with this software; if not, see -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -# -# Red Hat trademarks are not licensed under GPLv2. No permission is -# granted to use or replicate Red Hat trademarks that are incorporated -# in this software or its documentation. -# -from subscription_manager.i18n import ugettext as _ -from subscription_manager.cli_command.abstract_syspurpose import AbstractSyspurposeCommand -from subscription_manager.cli_command.org import OrgCommand - - -class AddonsCommand(AbstractSyspurposeCommand, OrgCommand): - def __init__(self, subparser=None): - shortdesc = _("Show or modify the system purpose addons setting") - super(AddonsCommand, self).__init__( - "addons", - subparser, - shortdesc=shortdesc, - primary=False, - attr="addons", - commands=["unset", "add", "remove", "show", "list"], - ) diff --git a/src/subscription_manager/cli_command/attach.py b/src/subscription_manager/cli_command/attach.py deleted file mode 100644 index 1796b164aa..0000000000 --- a/src/subscription_manager/cli_command/attach.py +++ /dev/null @@ -1,306 +0,0 @@ -# -# Subscription manager command line utility. -# -# Copyright (c) 2021 Red Hat, Inc. -# -# This software is licensed to you under the GNU General Public License, -# version 2 (GPLv2). There is NO WARRANTY for this software, express or -# implied, including the implied warranties of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -# along with this software; if not, see -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -# -# Red Hat trademarks are not licensed under GPLv2. No permission is -# granted to use or replicate Red Hat trademarks that are incorporated -# in this software or its documentation. -# -import fileinput -import logging -import os -import re - -import rhsm.connection as connection -import subscription_manager.injection as inj - -from rhsmlib.services import attach, products - -from subscription_manager.action_client import ProfileActionClient, ActionClient -from subscription_manager.cli import system_exit -from subscription_manager.cli_command.cli import CliCommand, handle_exception -from subscription_manager.cli_command.list import show_autosubscribe_output -from subscription_manager.exceptions import ExceptionMapper -from subscription_manager.i18n import ugettext as _ -from subscription_manager.packageprofilelib import PackageProfileActionInvoker -from subscription_manager.syspurposelib import save_sla_to_syspurpose_metadata -from subscription_manager.utils import is_simple_content_access, get_current_owner - -log = logging.getLogger(__name__) - - -class AttachCommand(CliCommand): - def __init__(self): - super(AttachCommand, self).__init__(self._command_name(), self._short_description(), self._primary()) - - self.product = None - self.substoken = None - self.auto_attach = True - self.parser.add_argument( - "--pool", - dest="pool", - action="append", - help=_("The ID of the pool to attach (can be specified more than once)"), - ) - self.parser.add_argument( - "--quantity", - dest="quantity", - type=int, - help=_("Number of subscriptions to attach. May not be used with an auto-attach."), - ) - self.parser.add_argument( - "--auto", - action="store_true", - help=_( - "Automatically attach the best-matched compatible subscriptions to this system. " - "This is the default action." - ), - ) - self.parser.add_argument( - "--servicelevel", - dest="service_level", - help=_( - "Automatically attach only subscriptions matching the specified service level; " - "only used with --auto" - ), - ) - self.parser.add_argument( - "--file", - dest="file", - help=_( - "A file from which to read pool IDs. If a hyphen is provided, pool IDs will be " - "read from stdin." - ), - ) - - # re bz #864207 - _("All installed products are covered by valid entitlements.") - _("No need to update subscriptions at this time.") - - def _read_pool_ids(self, f): - if not self.options.pool: - self.options.pool = [] - - for line in fileinput.input(f): - for pool in filter(bool, re.split(r"\s+", line.strip())): - self.options.pool.append(pool) - - def _short_description(self): - return _( - "Attach a specified subscription to the registered system, when system does not use " - "Simple Content Access mode" - ) - - def _command_name(self): - return "attach" - - def _primary(self): - return True - - def _validate_options(self): - if self.options.pool or self.options.file: - if self.options.auto: - system_exit(os.EX_USAGE, _("Error: --auto may not be used when specifying pools.")) - if self.options.service_level: - system_exit( - os.EX_USAGE, _("Error: The --servicelevel option cannot be used when specifying pools.") - ) - - # Quantity must be positive - if self.options.quantity is not None: - if self.options.quantity <= 0: - system_exit(os.EX_USAGE, _("Error: Quantity must be a positive integer.")) - elif self.options.auto or not (self.options.pool or self.options.file): - system_exit(os.EX_USAGE, _("Error: --quantity may not be used with an auto-attach")) - - # If a pools file was specified, process its contents and append it to options.pool - if self.options.file: - self.options.file = os.path.expanduser(self.options.file) - if self.options.file == "-" or os.path.isfile(self.options.file): - self._read_pool_ids(self.options.file) - - if len(self.options.pool) < 1: - if self.options.file == "-": - system_exit(os.EX_DATAERR, _("Error: Received data does not contain any pool IDs.")) - else: - system_exit( - os.EX_DATAERR, - _('Error: The file "{file}" does not contain any pool IDs.').format( - file=self.options.file - ), - ) - else: - system_exit( - os.EX_DATAERR, - _('Error: The file "{file}" does not exist or cannot be read.').format( - file=self.options.file - ), - ) - - def _print_ignore_attach_message(self): - """ - Print message about ignoring attach request - :return: None - """ - owner = get_current_owner(self.cp, self.identity) - owner_id = owner["key"] - print( - _( - "Ignoring the request to attach. " - 'Attaching subscriptions is disabled for organization "{owner_id}" ' - "because Simple Content Access (SCA) is enabled." - ).format(owner_id=owner_id) - ) - - def _do_command(self): - """ - Executes the command. - """ - self.assert_should_be_registered() - self._validate_options() - - # --pool or --file turns off default auto attach - if self.options.pool or self.options.file: - self.auto_attach = False - - # Do not try to do auto-attach, when simple content access mode is used - # BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1826300 - # - if is_simple_content_access(uep=self.cp, identity=self.identity): - if self.auto_attach is True: - self._print_ignore_auto_attach_message() - else: - self._print_ignore_attach_message() - return 0 - - installed_products_num = 0 - return_code = 0 - report = None - - # TODO: change to if self.auto_attach: else: pool/file stuff - try: - cert_action_client = ActionClient(skips=[PackageProfileActionInvoker]) - cert_action_client.update() - cert_update = True - - attach_service = attach.AttachService(self.cp) - if self.options.pool: - subscribed = False - - for pool in self.options.pool: - # odd html strings will cause issues, reject them here. - if pool.find("#") >= 0: - system_exit(os.EX_USAGE, _("Please enter a valid numeric pool ID.")) - - try: - ents = attach_service.attach_pool(pool, self.options.quantity) - # Usually just one, but may as well be safe: - for ent in ents: - pool_json = ent["pool"] - print( - _("Successfully attached a subscription for: {name}").format( - name=pool_json["productName"] - ) - ) - log.debug( - "Attached a subscription for {name}".format(name=pool_json["productName"]) - ) - subscribed = True - except connection.RestlibException as re: - log.exception(re) - - exception_mapper = ExceptionMapper() - mapped_message = exception_mapper.get_message(re) - - if re.code == 403: - print(mapped_message) # already subscribed. - elif re.code == 400 or re.code == 404: - print(mapped_message) # no such pool. - else: - system_exit(os.EX_SOFTWARE, mapped_message) # some other error.. don't try again - if not subscribed: - return_code = 1 - # must be auto - else: - installed_products_num = len(products.InstalledProducts(self.cp).list()) - # if we are green, we don't need to go to the server - self.sorter = inj.require(inj.CERT_SORTER) - - if self.sorter.is_valid(): - if not installed_products_num: - print(_("No Installed products on system. " "No need to attach subscriptions.")) - else: - print( - _( - "All installed products are covered by valid entitlements. " - "No need to update subscriptions at this time." - ) - ) - cert_update = False - else: - # If service level specified, make an additional request to - # verify service levels are supported on the server: - if self.options.service_level: - consumer = self.cp.getConsumer(self.identity.uuid) - if "serviceLevel" not in consumer: - system_exit( - os.EX_UNAVAILABLE, - _( - "Error: The --servicelevel option is not " - "supported by the server. Did not " - "complete your request." - ), - ) - - attach_service.attach_auto(self.options.service_level) - if self.options.service_level is not None: - # RHBZ 1632797 we should only save the sla if the sla was actually - # specified. The uep and consumer_uuid are None, because service_level was sent - # to candlepin server using attach_service.attach_auto() - save_sla_to_syspurpose_metadata( - uep=None, consumer_uuid=None, service_level=self.options.service_level - ) - print(_("Service level set to: {}").format(self.options.service_level)) - - if cert_update: - report = self.entcertlib.update() - - profile_action_client = ProfileActionClient() - profile_action_client.update() - - if report and report.exceptions(): - print(_("Entitlement Certificate(s) update failed due to the following reasons:")) - for e in report.exceptions(): - print("\t-", str(e)) - elif self.auto_attach: - if not installed_products_num: - return_code = 1 - else: - self.sorter.force_cert_check() - # Make sure that we get fresh status of installed products - status_cache = inj.require(inj.ENTITLEMENT_STATUS_CACHE) - status_cache.load_status( - self.sorter.cp_provider.get_consumer_auth_cp(), - self.sorter.identity.uuid, - self.sorter.on_date, - ) - self.sorter.load() - # run this after entcertlib update, so we have the new entitlements - return_code = show_autosubscribe_output(self.cp, self.identity) - - except Exception as e: - handle_exception("Unable to attach: {e}".format(e=e), e) - - # it is okay to call this no matter what happens above, - # it's just a notification to perform a check - self._request_validity_check() - - return return_code diff --git a/src/subscription_manager/cli_command/autoheal.py b/src/subscription_manager/cli_command/autoheal.py deleted file mode 100644 index d4ead1f2fd..0000000000 --- a/src/subscription_manager/cli_command/autoheal.py +++ /dev/null @@ -1,70 +0,0 @@ -# -# Subscription manager command line utility. -# -# Copyright (c) 2021 Red Hat, Inc. -# -# This software is licensed to you under the GNU General Public License, -# version 2 (GPLv2). There is NO WARRANTY for this software, express or -# implied, including the implied warranties of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -# along with this software; if not, see -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -# -# Red Hat trademarks are not licensed under GPLv2. No permission is -# granted to use or replicate Red Hat trademarks that are incorporated -# in this software or its documentation. -# -import subscription_manager.injection as inj - -from subscription_manager.cli_command.cli import CliCommand -from subscription_manager.i18n import ugettext as _ - - -class AutohealCommand(CliCommand): - def __init__(self): - self.uuid = inj.require(inj.IDENTITY).uuid - - shortdesc = _("Set if subscriptions are attached on a schedule (default of daily)") - self._org_help_text = _("specify whether to enable or disable auto-attaching of subscriptions") - super(AutohealCommand, self).__init__("auto-attach", shortdesc, False) - - self.parser.add_argument( - "--enable", - dest="enable", - action="store_true", - help=_("try to attach subscriptions for uncovered products each check-in"), - ) - self.parser.add_argument( - "--disable", - dest="disable", - action="store_true", - help=_("do not try to automatically attach subscriptions each check-in"), - ) - self.parser.add_argument( - "--show", - dest="show", - action="store_true", - help=_("show the current auto-attach preference"), - ) - - def _toggle(self, autoheal): - self.cp.updateConsumer(self.uuid, autoheal=autoheal) - self._show(autoheal) - - def _validate_options(self): - if not self.uuid: - self.assert_should_be_registered() - - def _show(self, autoheal): - if autoheal: - print(_("Auto-attach preference: enabled")) - else: - print(_("Auto-attach preference: disabled")) - - def _do_command(self): - self._validate_options() - - if not self.options.enable and not self.options.disable: - self._show(self.cp.getConsumer(self.uuid)["autoheal"]) - else: - self._toggle(self.options.enable or False) diff --git a/src/subscription_manager/cli_command/cli.py b/src/subscription_manager/cli_command/cli.py index a64aa294d0..8e3324712d 100644 --- a/src/subscription_manager/cli_command/cli.py +++ b/src/subscription_manager/cli_command/cli.py @@ -44,7 +44,6 @@ format_baseurl, is_valid_server_info, MissingCaCertException, - get_current_owner, ) ERR_NOT_REGISTERED_MSG = _( @@ -115,25 +114,6 @@ def __init__(self, name="cli", shortdesc=None, primary=False): self.correlation_id = generate_correlation_id() - def _print_ignore_auto_attach_message(self): - """ - This message is shared by attach command and register command, because - both commands can do auto-attach. - :return: None - """ - owner = get_current_owner(self.cp, self.identity) - # We displayed Owner name: `owner_name = owner['displayName']`, but such behavior - # was not consistent with rest of subscription-manager - # Look at this comment: https://bugzilla.redhat.com/show_bug.cgi?id=1826300#c8 - owner_id = owner["key"] - print( - _( - "Ignoring the request to auto-attach. " - 'Attaching subscriptions is disabled for organization "{owner_id}" ' - "because Simple Content Access (SCA) is enabled." - ).format(owner_id=owner_id) - ) - def _get_logger(self): return logging.getLogger( "rhsm-app.{module}.{name}".format(module=self.__module__, name=self.__class__.__name__) diff --git a/src/subscription_manager/cli_command/environments.py b/src/subscription_manager/cli_command/environments.py index ec645e46be..d6a57089b8 100644 --- a/src/subscription_manager/cli_command/environments.py +++ b/src/subscription_manager/cli_command/environments.py @@ -26,7 +26,6 @@ ERR_NOT_REGISTERED_MSG, ) from subscription_manager.cli_command.org import OrgCommand -from subscription_manager.cli_command.list import ENVIRONMENT_LIST from subscription_manager.i18n import ugettext as _ from subscription_manager.i18n import ungettext from subscription_manager.printing_utils import columnize, echo_columnize_callback @@ -36,8 +35,14 @@ log = logging.getLogger(__name__) + MULTI_ENV = "multi_environment" +ENVIRONMENT_LIST = [ + _("Name:"), + _("Description:"), +] + class EnvironmentsCommand(OrgCommand): def __init__(self): @@ -89,14 +94,11 @@ def _do_command(self): if "environments" not in supported_resources: system_exit(os.EX_UNAVAILABLE, _("Error: Server does not support environments.")) try: - if self.options.token: - self.cp = self.cp_provider.get_keycloak_auth_cp(self.options.token) - else: - if not self.options.enabled: - if self.options.username is None or self.options.password is None: - print(_("This operation requires user credentials")) - self.cp_provider.set_user_pass(self.username, self.password) - self.cp = self.cp_provider.get_basic_auth_cp() + if not self.options.enabled: + if self.options.username is None or self.options.password is None: + print(_("This operation requires user credentials")) + self.cp_provider.set_user_pass(self.username, self.password) + self.cp = self.cp_provider.get_basic_auth_cp() self.identity = require(IDENTITY) if self.options.set: self._set_environments() diff --git a/src/subscription_manager/cli_command/identity.py b/src/subscription_manager/cli_command/identity.py index 430626a823..b6804818d1 100644 --- a/src/subscription_manager/cli_command/identity.py +++ b/src/subscription_manager/cli_command/identity.py @@ -61,8 +61,6 @@ def _validate_options(self): system_exit(os.EX_USAGE, _("--force can only be used with --regenerate")) if (self.options.username or self.options.password) and not self.options.force: system_exit(os.EX_USAGE, _("--username and --password can only be used with --force")) - if self.options.token and not self.options.force: - system_exit(os.EX_USAGE, _("--token can only be used with --force")) def _do_command(self): # get current consumer identity @@ -114,12 +112,9 @@ def _do_command(self): ) else: if self.options.force: - # get an UEP with basic auth or keycloak auth - if self.options.token: - self.cp = self.cp_provider.get_keycloak_auth_cp(self.options.token) - else: - self.cp_provider.set_user_pass(self.username, self.password) - self.cp = self.cp_provider.get_basic_auth_cp() + # get an UEP with basic auth + self.cp_provider.set_user_pass(self.username, self.password) + self.cp = self.cp_provider.get_basic_auth_cp() consumer = self.cp.regenIdCertificate(consumerid) managerlib.persist_consumer_cert(consumer) diff --git a/src/subscription_manager/cli_command/list.py b/src/subscription_manager/cli_command/list.py index 3c0915fa90..efc17ff7d9 100644 --- a/src/subscription_manager/cli_command/list.py +++ b/src/subscription_manager/cli_command/list.py @@ -14,56 +14,16 @@ # granted to use or replicate Red Hat trademarks that are incorporated # in this software or its documentation. # -import datetime -import logging -import os -import sys - -from time import localtime, strftime, strptime - -from rhsmlib.services import products, entitlement -from subscription_manager.cert_sorter import ( - FUTURE_SUBSCRIBED, - SUBSCRIBED, - NOT_SUBSCRIBED, - EXPIRED, - PARTIALLY_SUBSCRIBED, - UNKNOWN, -) -from subscription_manager.cli import system_exit +import logging +from rhsmlib.services import products from subscription_manager.cli_command.cli import CliCommand from subscription_manager.i18n import ugettext as _ -from subscription_manager.jsonwrapper import PoolWrapper + from subscription_manager.printing_utils import ( columnize, none_wrap_columnize_callback, - highlight_by_filter_string_columnize_cb, - echo_columnize_callback, ) -from subscription_manager.utils import is_simple_content_access - -# Translates the cert sorter status constants: - -STATUS_MAP = { - FUTURE_SUBSCRIBED: _("Future Subscription"), - SUBSCRIBED: _("Subscribed"), - NOT_SUBSCRIBED: _("Not Subscribed"), - EXPIRED: _("Expired"), - PARTIALLY_SUBSCRIBED: _("Partially Subscribed"), - UNKNOWN: _("Unknown"), -} - -INSTALLED_PRODUCT_STATUS = [ - _("Product Name:"), - _("Product ID:"), - _("Version:"), - _("Arch:"), - _("Status:"), - _("Status Details:"), - _("Starts:"), - _("Ends:"), -] INSTALLED_PRODUCT_STATUS_SCA = [ _("Product Name:"), @@ -72,481 +32,56 @@ _("Arch:"), ] -AVAILABLE_SUBS_LIST = [ - _("Subscription Name:"), - _("Provides:"), - _("SKU:"), - _("Contract:"), - _("Pool ID:"), - _("Provides Management:"), - _("Available:"), - _("Suggested:"), - _("Service Type:"), - _("Roles:"), - _("Service Level:"), - _("Usage:"), - _("Add-ons:"), - _("Subscription Type:"), - _("Starts:"), - _("Ends:"), - _("Entitlement Type:"), -] - -AVAILABLE_SUBS_MATCH_COLUMNS = [ - _("Subscription Name:"), - _("Provides:"), - _("SKU:"), - _("Contract:"), - _("Service Level:"), -] - -REPOS_LIST = [ - _("Repo ID:"), - _("Repo Name:"), - _("Repo URL:"), - _("Enabled:"), -] - -PRODUCT_STATUS = [ - _("Product Name:"), - _("Status:"), -] - -ENVIRONMENT_LIST = [ - _("Name:"), - _("Description:"), -] - -ORG_LIST = [ - _("Name:"), - _("Key:"), -] - -OLD_CONSUMED_LIST = [ - _("Subscription Name:"), - _("Provides:"), - _("SKU:"), - _("Contract:"), - _("Account:"), - _("Serial:"), - _("Pool ID:"), - _("Provides Management:"), - _("Active:"), - _("Quantity Used:"), - _("Service Type:"), - _("Service Level:"), - _("Status Details:"), - _("Subscription Type:"), - _("Starts:"), - _("Ends:"), - _("System Type:"), -] - -CONSUMED_LIST = [ - _("Subscription Name:"), - _("Provides:"), - _("SKU:"), - _("Contract:"), - _("Account:"), - _("Serial:"), - _("Pool ID:"), - _("Provides Management:"), - _("Active:"), - _("Quantity Used:"), - _("Service Type:"), - _("Roles:"), - _("Service Level:"), - _("Usage:"), - _("Add-ons:"), - _("Status Details:"), - _("Subscription Type:"), - _("Starts:"), - _("Ends:"), - _("Entitlement Type:"), -] log = logging.getLogger(__name__) -def show_autosubscribe_output(uep, identity): - """ - Try to show auto-attach output - :param uep: object with connection to candlepin - :param identity: object with identity - :return: return 1, when all installed products are subscribed, otherwise return 0 - """ - - if is_simple_content_access(uep=uep, identity=identity): - return 0 - - installed_products = products.InstalledProducts(uep).list() - - if not installed_products: - # Returning an error code here breaks registering when no products are installed, and the - # AttachCommand already performs this check before calling. - print(_("No products installed.")) - return 0 - - log.debug("Attempted to auto-attach/heal the system.") - print(_("Installed Product Current Status:")) - subscribed = 1 - all_subscribed = True - for product in installed_products: - if product[4] == SUBSCRIBED: - subscribed = 0 - status = STATUS_MAP[product[4]] - if product[4] == NOT_SUBSCRIBED: - all_subscribed = False - print(columnize(PRODUCT_STATUS, echo_columnize_callback, product[0], status) + "\n") - if not all_subscribed: - print(_("Unable to find available subscriptions for all your installed products.")) - return subscribed - - class ListCommand(CliCommand): def __init__(self): shortdesc = _("List subscription and product information for this system") super(ListCommand, self).__init__("list", shortdesc, True) - self.available = None - self.consumed = None self.parser.add_argument( "--installed", action="store_true", help=_("list shows those products which are installed (default)"), ) - self.parser.add_argument( - "--available", - action="store_true", - help=_("show those subscriptions which are available"), - ) - self.parser.add_argument( - "--all", - action="store_true", - help=_("used with --available to ensure all subscriptions are returned"), - ) - self.parser.add_argument( - "--ondate", - dest="on_date", - help=_( - "date to search on, defaults to today's date, only used with --available (example: {example})" - ).format(example=strftime("%Y-%m-%d", localtime())), - ) - self.parser.add_argument( - "--consumed", - action="store_true", - help=_("show the subscriptions being consumed by this system"), - ) - self.parser.add_argument( - "--servicelevel", - dest="service_level", - help=_( - "shows only subscriptions matching the specified service level; " - "only used with --available and --consumed" - ), - ) - self.parser.add_argument( - "--no-overlap", - action="store_true", - help=_( - "shows pools which provide products that are not already covered; " - "only used with --available" - ), - ) - self.parser.add_argument( - "--match-installed", - action="store_true", - help=_( - "shows only subscriptions matching products that are currently installed; " - "only used with --available" - ), - ) self.parser.add_argument( "--matches", dest="filter_string", - help=_( - "lists only subscriptions or products containing the specified expression " - "in the subscription or product information, varying with the list requested " - "and the server version (case-insensitive)." - ), - ) - self.parser.add_argument( - "--pool-only", - dest="pid_only", - action="store_true", - help=_( - "lists only the pool IDs for applicable available or consumed subscriptions; " - "only used with --available and --consumed" - ), - ) - self.parser.add_argument( - "--afterdate", - dest="after_date", - help=_( - "show pools that are active on or after the given date; " - "only used with --available (example: {example})" - ).format(example=strftime("%Y-%m-%d", localtime())), + help=_("lists only products containing the specified expression in the product information."), ) - def _validate_options(self): - if self.options.all and not self.options.available: - system_exit(os.EX_USAGE, _("Error: --all is only applicable with --available")) - if self.options.on_date and not self.options.available: - system_exit(os.EX_USAGE, _("Error: --ondate is only applicable with --available")) - if self.options.service_level is not None and not (self.options.consumed or self.options.available): - system_exit( - os.EX_USAGE, _("Error: --servicelevel is only applicable with --available or --consumed") - ) - if not (self.options.available or self.options.consumed): - self.options.installed = True - if not self.options.available and self.options.match_installed: - system_exit(os.EX_USAGE, _("Error: --match-installed is only applicable with --available")) - if self.options.no_overlap and not self.options.available: - system_exit(os.EX_USAGE, _("Error: --no-overlap is only applicable with --available")) - if self.options.pid_only and self.options.installed: - system_exit( - os.EX_USAGE, _("Error: --pool-only is only applicable with --available and/or --consumed") - ) - if self.options.after_date and not self.options.available: - system_exit(os.EX_USAGE, _("Error: --afterdate is only applicable with --available")) - if self.options.after_date and self.options.on_date: - system_exit(os.EX_USAGE, _("Error: --afterdate cannot be used with --ondate")) - - def _parse_date(self, date): - """ - Turns a given date into a date object - :param date: Date string - :type date: str - :return: date - """ - try: - # doing it this ugly way for pre python 2.5 - return datetime.datetime(*(strptime(date, "%Y-%m-%d")[0:6])) - except Exception: - # Translators: dateexample is current date in format like 2014-11-31 - msg = _( - "Date entered is invalid. Date should be in YYYY-MM-DD format (example: {" "dateexample})" - ) - dateexample = strftime("%Y-%m-%d", localtime()) - system_exit(os.EX_DATAERR, msg.format(dateexample=dateexample)) - - def _split_mulit_value_field(self, values): - """ - REST API returns multi-value fields in string, where values are separated with comma, but - each value of multi-value field should be printed on new line. It is done automatically, when - values are in list - :param values: String containing multi-value string, where values are separated with comma - :return: list of values - """ - if values is None: - return "" - return [item.strip() for item in values.split(",")] - def _do_command(self): """ Executes the command. """ self._validate_options() - if self.options.installed and not self.options.pid_only: - installed_products = products.InstalledProducts(self.cp).list(self.options.filter_string) - - if len(installed_products): - print("+-------------------------------------------+") - print(_(" Installed Product Status")) - print("+-------------------------------------------+") - - for product in installed_products: - if is_simple_content_access(self.cp, self.identity): - print( - columnize( - INSTALLED_PRODUCT_STATUS_SCA, - none_wrap_columnize_callback, - product[0], # Name - product[1], # ID - product[2], # Version - product[3], # Arch - ) - + "\n" - ) - else: - status = STATUS_MAP[product[4]] - print( - columnize( - INSTALLED_PRODUCT_STATUS, - none_wrap_columnize_callback, - product[0], # Name - product[1], # ID - product[2], # Version - product[3], # Arch - status, # Status - product[5], # Status details - product[6], # Start - product[7], # End - ) - + "\n" - ) - else: - if self.options.filter_string: - print( - _('No installed products were found matching the expression "{filter}".').format( - filter=self.options.filter_string - ) - ) - else: - print(_("No installed products to list")) + installed_products = products.InstalledProducts(self.cp).list(self.options.filter_string) - if self.options.available: - self.assert_should_be_registered() - on_date = None - after_date = None - if self.options.on_date: - on_date = self._parse_date(self.options.on_date) - elif self.options.after_date: - after_date = self._parse_date(self.options.after_date) + if len(installed_products): + print("+-------------------------------------------+") + print(_(" Installed Product Status")) + print("+-------------------------------------------+") - epools = entitlement.EntitlementService().get_available_pools( - show_all=self.options.all, - on_date=on_date, - no_overlap=self.options.no_overlap, - match_installed=self.options.match_installed, - matches=self.options.filter_string, - service_level=self.options.service_level, - after_date=after_date, - ) - - if len(epools): - if self.options.pid_only: - for data in epools: - print(data["id"]) - else: - print("+-------------------------------------------+") - print(" " + _("Available Subscriptions")) - print("+-------------------------------------------+") - - for data in epools: - if PoolWrapper(data).is_virt_only(): - entitlement_type = _("Virtual") - else: - entitlement_type = _("Physical") - - if "management_enabled" in data and data["management_enabled"]: - data["management_enabled"] = _("Yes") - else: - data["management_enabled"] = _("No") - - kwargs = { - "filter_string": self.options.filter_string, - "match_columns": AVAILABLE_SUBS_MATCH_COLUMNS, - "is_atty": sys.stdout.isatty(), - } - print( - columnize( - AVAILABLE_SUBS_LIST, - highlight_by_filter_string_columnize_cb, - data["productName"], - data["providedProducts"], - data["productId"], - data["contractNumber"] or "", - data["id"], - data["management_enabled"], - data["quantity"], - data["suggested"], - data["service_type"] or "", - self._split_mulit_value_field(data["roles"]), - data["service_level"] or "", - data["usage"] or "", - self._split_mulit_value_field(data["addons"]), - data["pool_type"], - data["startDate"], - data["endDate"], - entitlement_type, - **kwargs - ) - + "\n" - ) - elif not self.options.pid_only: - if self.options.filter_string and self.options.service_level: - print( - _( - "No available subscription pools were found matching the expression " - '"{filter}" and the service level "{level}".' - ).format(filter=self.options.filter_string, level=self.options.service_level) - ) - elif self.options.filter_string: - print( - _( - 'No available subscription pools were found matching the expression "{filter}".' - ).format(filter=self.options.filter_string) - ) - elif self.options.service_level: - print( - _( - 'No available subscription pools were found matching the service level "{level}".' - ).format(level=self.options.service_level) - ) - else: - print(_("No available subscription pools to list")) - - if self.options.consumed: - self.print_consumed( - service_level=self.options.service_level, - filter_string=self.options.filter_string, - pid_only=self.options.pid_only, - ) - - def print_consumed(self, service_level=None, filter_string=None, pid_only=False): - # list all certificates that have not yet expired, even those - # that are not yet active. - service = entitlement.EntitlementService() - certs = service.get_consumed_product_pools(service_level=service_level, matches=filter_string) - - # Process and display our (filtered) certs: - if len(certs): - if pid_only: - for cert in certs: - print(cert.pool_id) - else: - print("+-------------------------------------------+") - print(" " + _("Consumed Subscriptions")) - print("+-------------------------------------------+") - - for cert in certs: - kwargs = { - "filter_string": filter_string, - "match_columns": AVAILABLE_SUBS_MATCH_COLUMNS, - "is_atty": sys.stdout.isatty(), - } - if hasattr(cert, "roles") and hasattr(cert, "usage") and hasattr(cert, "addons"): - print( - columnize(CONSUMED_LIST, highlight_by_filter_string_columnize_cb, *cert, **kwargs) - + "\n" - ) - else: - print( - columnize( - OLD_CONSUMED_LIST, highlight_by_filter_string_columnize_cb, *cert, **kwargs - ) - + "\n" - ) - elif not pid_only: - if filter_string and service_level: + for product in installed_products: print( - _( - 'No consumed subscription pools were found matching the expression "{filter}" ' - 'and the service level "{level}".' - ).format(filter=filter_string, level=service_level) - ) - elif filter_string: - print( - _('No consumed subscription pools were found matching the expression "{filter}".').format( - filter=filter_string + columnize( + INSTALLED_PRODUCT_STATUS_SCA, + none_wrap_columnize_callback, + product[0], # Name + product[1], # ID + product[2], # Version + product[3], # Arch ) + + "\n" ) - elif service_level: + else: + if self.options.filter_string: print( - _( - 'No consumed subscription pools were found matching the service level "{level}".' - ).format(level=service_level) + _('No installed products were found matching the expression "{filter}".').format( + filter=self.options.filter_string + ) ) else: - print(_("No consumed subscription pools were found.")) + print(_("No installed products to list")) diff --git a/src/subscription_manager/cli_command/override.py b/src/subscription_manager/cli_command/override.py index 85abb4b0e0..d17c7122ee 100644 --- a/src/subscription_manager/cli_command/override.py +++ b/src/subscription_manager/cli_command/override.py @@ -18,12 +18,12 @@ import rhsm.connection as connection +from rhsm.repofile import manage_repos_enabled from subscription_manager.cli import system_exit from subscription_manager.cli_command.cli import CliCommand from subscription_manager.i18n import ugettext as _ from subscription_manager.overrides import Overrides, Override from subscription_manager.printing_utils import columnize, echo_columnize_callback -from subscription_manager.repofile import manage_repos_enabled from subscription_manager.utils import get_supported_resources diff --git a/src/subscription_manager/cli_command/owners.py b/src/subscription_manager/cli_command/owners.py index 43a64210dd..761673f89d 100644 --- a/src/subscription_manager/cli_command/owners.py +++ b/src/subscription_manager/cli_command/owners.py @@ -21,13 +21,17 @@ from subscription_manager.cli import system_exit from subscription_manager.cli_command.cli import handle_exception -from subscription_manager.cli_command.list import ORG_LIST from subscription_manager.cli_command.user_pass import UserPassCommand from subscription_manager.i18n import ugettext as _ from subscription_manager.printing_utils import columnize, echo_columnize_callback log = logging.getLogger(__name__) +ORG_LIST = [ + _("Name:"), + _("Key:"), +] + class OwnersCommand(UserPassCommand): def __init__(self): @@ -40,11 +44,8 @@ def __init__(self): def _do_command(self): try: # get a UEP - if self.options.token: - self.cp = self.cp_provider.get_keycloak_auth_cp(self.options.token) - else: - self.cp_provider.set_user_pass(self.username, self.password) - self.cp = self.cp_provider.get_basic_auth_cp() + self.cp_provider.set_user_pass(self.username, self.password) + self.cp = self.cp_provider.get_basic_auth_cp() owners = self.cp.getOwnerList(self.username) log.debug("Successfully retrieved org list from server.") if len(owners): diff --git a/src/subscription_manager/cli_command/redeem.py b/src/subscription_manager/cli_command/redeem.py deleted file mode 100644 index fe74273d68..0000000000 --- a/src/subscription_manager/cli_command/redeem.py +++ /dev/null @@ -1,90 +0,0 @@ -# -# Subscription manager command line utility. -# -# Copyright (c) 2021 Red Hat, Inc. -# -# This software is licensed to you under the GNU General Public License, -# version 2 (GPLv2). There is NO WARRANTY for this software, express or -# implied, including the implied warranties of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -# along with this software; if not, see -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -# -# Red Hat trademarks are not licensed under GPLv2. No permission is -# granted to use or replicate Red Hat trademarks that are incorporated -# in this software or its documentation. -# -import os - -import rhsm.connection as connection -import subscription_manager.injection as inj - -from subscription_manager.cli import system_exit -from subscription_manager.cli_command.cli import CliCommand, handle_exception -from subscription_manager.i18n import ugettext as _ - - -class RedeemCommand(CliCommand): - def __init__(self): - shortdesc = _("Attempt to redeem a subscription for a preconfigured system") - super(RedeemCommand, self).__init__("redeem", shortdesc, False) - - self.parser.add_argument( - "--email", - dest="email", - action="store", - help=_("email address to notify when " "subscription redemption is complete"), - ) - self.parser.add_argument( - "--locale", - dest="locale", - action="store", - help=_( - "optional language to use for email " - "notification when subscription redemption is " - "complete (Examples: en-us, de-de)" - ), - ) - - def _validate_options(self): - if not self.options.email: - system_exit( - os.EX_USAGE, _("Error: This command requires that you specify an email address with --email.") - ) - - def _do_command(self): - """ - Executes the command. - """ - self.assert_should_be_registered() - self._validate_options() - - try: - # FIXME: why just facts and package profile update here? - # update facts first, if we need to - facts = inj.require(inj.FACTS) - facts.update_check(self.cp, self.identity.uuid) - - profile_mgr = inj.require(inj.PROFILE_MANAGER) - profile_mgr.update_check(self.cp, self.identity.uuid) - - # BZ 1248833 Ensure we print out the display message if we get any back - response = self.cp.activateMachine(self.identity.uuid, self.options.email, self.options.locale) - if response is None: - system_exit(os.EX_SOFTWARE, _("Error: Unable to redeem subscription for this system.")) - if response.get("displayMessage"): - system_exit(0, response.get("displayMessage")) - except connection.GoneException as ge: - raise ge - except connection.RestlibException as e: - # candlepin throws an exception during activateMachine, even for - # 200's. We need to look at the code in the RestlibException and proceed - # accordingly - if 200 <= e.code <= 210: - system_exit(0, e) - else: - handle_exception("Unable to redeem: {e}".format(e=e), e) - except Exception as e: - handle_exception("Unable to redeem: {e}".format(e=e), e) - - self._request_validity_check() diff --git a/src/subscription_manager/cli_command/register.py b/src/subscription_manager/cli_command/register.py index 0c7ae9bd46..a2b1e6e40d 100644 --- a/src/subscription_manager/cli_command/register.py +++ b/src/subscription_manager/cli_command/register.py @@ -29,27 +29,23 @@ from rhsm.utils import LiveStatusMessage from rhsmlib.facts.hwprobe import ClassicCheck -from rhsmlib.services import attach, unregister, register, exceptions +from rhsmlib.services import unregister, register, exceptions from subscription_manager import identity from subscription_manager.branding import get_branding from subscription_manager.cli import system_exit from subscription_manager.cli_command.cli import handle_exception, conf from subscription_manager.cli_command.environments import MULTI_ENV -from subscription_manager.cli_command.list import show_autosubscribe_output from subscription_manager.cli_command.user_pass import UserPassCommand from subscription_manager.entcertlib import CONTENT_ACCESS_CERT_CAPABILITY from subscription_manager.i18n import ugettext as _ from subscription_manager.utils import ( restart_virt_who, - print_error, get_supported_resources, - is_simple_content_access, is_interactive, is_process_running, ) from subscription_manager.cli_command.environments import check_set_environment_names -from subscription_manager.exceptions import ExceptionMapper log = logging.getLogger(__name__) @@ -106,17 +102,6 @@ def __init__(self): dest="release", help=_("set a release version"), ) - self.parser.add_argument( - "--autosubscribe", - action="store_true", - help=_("Deprecated, see --auto-attach"), - ) - self.parser.add_argument( - "--auto-attach", - action="store_true", - dest="autoattach", - help=_("automatically attach compatible subscriptions to this system"), - ) self.parser.add_argument( "--force", action="store_true", @@ -128,19 +113,13 @@ def __init__(self): dest="activation_keys", help=_("activation key to use for registration (can be specified more than once)"), ) - self.parser.add_argument( - "--servicelevel", - dest="service_level", - help=_("system preference used when subscribing automatically, requires --auto-attach"), - ) def _validate_options(self): - self.autoattach = self.options.autosubscribe or self.options.autoattach if self.is_registered() and not self.options.force: system_exit(os.EX_USAGE, _("This system is already registered. Use --force to override")) elif self.options.consumername == "": system_exit(os.EX_USAGE, _("Error: system name can not be empty.")) - elif (self.options.username or self.options.token) and self.options.activation_keys: + elif self.options.username and self.options.activation_keys: system_exit(os.EX_USAGE, _("Error: Activation keys do not require user credentials.")) elif self.options.consumerid and self.options.activation_keys: system_exit( @@ -148,13 +127,9 @@ def _validate_options(self): ) elif self.options.environments and self.options.activation_keys: system_exit(os.EX_USAGE, _("Error: Activation keys do not allow environments to be specified.")) - elif self.autoattach and self.options.activation_keys: - system_exit(os.EX_USAGE, _("Error: Activation keys cannot be used with --auto-attach.")) # 746259: Don't allow the user to pass in an empty string as an activation key elif self.options.activation_keys and "" in self.options.activation_keys: system_exit(os.EX_USAGE, _("Error: Must specify an activation key")) - elif self.options.service_level and not self.autoattach: - system_exit(os.EX_USAGE, _("Error: Must use --auto-attach with --servicelevel.")) elif self.options.activation_keys and not self.options.org: system_exit(os.EX_USAGE, _("Error: Must provide --org with activation keys.")) elif self.options.force and self.options.consumerid: @@ -182,37 +157,6 @@ def persist_server_options(self): """ return True - def _do_auto_attach(self, consumer): - """ - Try to do auto-attach, when it was requested using --auto-attach CLI option - :return: None - """ - - # Do not try to do auto-attach, when simple content access mode is used - # Only print info message to stdout - if is_simple_content_access(uep=self.cp, identity=self.identity): - self._print_ignore_auto_attach_message() - return - - if "serviceLevel" not in consumer and self.options.service_level: - system_exit( - os.EX_UNAVAILABLE, - _( - "Error: The --servicelevel option is not supported " - "by the server. Did not complete your request." - ), - ) - try: - # We don't call auto_attach with self.option.service_level, because it has been already - # set during service.register() call - attach.AttachService(self.cp).attach_auto(service_level=None) - except connection.RestlibException as rest_lib_err: - mapped_message: str = ExceptionMapper().get_message(rest_lib_err) - print_error(mapped_message) - except Exception: - log.exception("Auto-attach failed") - raise - def _upload_profile_blocking(self, consumer: dict) -> None: """ Try to upload DNF profile to server @@ -313,9 +257,7 @@ def _do_command(self): # Proceed with new registration: try: - if self.options.token: - admin_cp = self.cp_provider.get_keycloak_auth_cp(self.options.token) - elif not self.options.activation_keys: + if not self.options.activation_keys: hostname = conf["server"]["hostname"] if ":" in hostname: normalized_hostname = "[{hostname}]".format(hostname=hostname) @@ -355,7 +297,6 @@ def _do_command(self): force=self.options.force, name=self.options.consumername, consumer_type=self.options.consumertype, - service_level=self.options.service_level, ) except (connection.RestlibException, exceptions.ServiceError) as re: log.exception(re) @@ -367,8 +308,6 @@ def _do_command(self): consumer_info = identity.ConsumerIdentity(consumer["idCert"]["key"], consumer["idCert"]["cert"]) print(_("The system has been registered with ID: {id}").format(id=consumer_info.getConsumerId())) print(_("The registered system name is: {name}").format(name=consumer_info.getConsumerName())) - if self.options.service_level: - print(_("Service level set to: {level}").format(level=self.options.service_level)) # We have new credentials, restart virt-who restart_virt_who() @@ -402,13 +341,9 @@ def _do_command(self): # TODO: grab the list of valid options, and check self.cp.updateConsumer(consumer["uuid"], release=self.options.release) - if self.autoattach: - self._do_auto_attach(consumer) - if ( self.options.consumerid or self.options.activation_keys - or self.autoattach or self.cp.has_capability(CONTENT_ACCESS_CERT_CAPABILITY) ): log.debug("System registered, updating entitlements if needed") @@ -418,15 +353,7 @@ def _do_command(self): self._upload_profile(consumer) - subscribed = 0 - if self.options.activation_keys or self.autoattach: - # update with the latest cert info - self.sorter = inj.require(inj.CERT_SORTER) - self.sorter.force_cert_check() - subscribed = show_autosubscribe_output(self.cp, self.identity) - self._request_validity_check() - return subscribed def _prompt_for_environment(self): """ diff --git a/src/subscription_manager/cli_command/remove.py b/src/subscription_manager/cli_command/remove.py deleted file mode 100644 index 3e58b1dd48..0000000000 --- a/src/subscription_manager/cli_command/remove.py +++ /dev/null @@ -1,212 +0,0 @@ -# -# Subscription manager command line utility. -# -# Copyright (c) 2021 Red Hat, Inc. -# -# This software is licensed to you under the GNU General Public License, -# version 2 (GPLv2). There is NO WARRANTY for this software, express or -# implied, including the implied warranties of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -# along with this software; if not, see -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -# -# Red Hat trademarks are not licensed under GPLv2. No permission is -# granted to use or replicate Red Hat trademarks that are incorporated -# in this software or its documentation. -# -import logging -import os - -import rhsm.connection as connection - -from rhsmlib.services import entitlement - -from subscription_manager.cli import system_exit -from subscription_manager.cli_command.cli import CliCommand, handle_exception -from subscription_manager.i18n import ungettext, ugettext as _ -from subscription_manager.utils import unique_list_items - -log = logging.getLogger(__name__) - - -class RemoveCommand(CliCommand): - def __init__(self): - super(RemoveCommand, self).__init__(self._command_name(), self._short_description(), self._primary()) - - self.parser.add_argument( - "--serial", - action="append", - dest="serials", - metavar="SERIAL", - help=_("certificate serial number to remove (can be specified more than once)"), - ) - self.parser.add_argument( - "--pool", - action="append", - dest="pool_ids", - metavar="POOL_ID", - help=_("the ID of the pool to remove (can be specified more than once)"), - ) - self.parser.add_argument( - "--all", - dest="all", - action="store_true", - help=_("remove all subscriptions from this system"), - ) - - def _short_description(self): - return _("Remove all or specific subscriptions from this system") - - def _command_name(self): - return "remove" - - def _primary(self): - return True - - def _validate_options(self): - if self.options.serials: - bad = False - for serial in self.options.serials: - if not serial.isdigit(): - print(_("Error: '{serial}' is not a valid serial number").format(serial=serial)) - bad = True - if bad: - system_exit(os.EX_USAGE) - elif self.options.pool_ids: - if not self.cp.has_capability("remove_by_pool_id"): - system_exit( - os.EX_UNAVAILABLE, - _( - "Error: The registered entitlement server does not support remove --pool." - "\nInstead, use the remove --serial option." - ), - ) - elif not self.options.all and not self.options.pool_ids: - system_exit( - os.EX_USAGE, - _("Error: This command requires that you specify one of --serial, --pool or --all."), - ) - - def _print_unbind_ids_result(self, success, failure, id_name): - if success: - if id_name == "pools": - print(_("The entitlement server successfully removed these pools:")) - elif id_name == "serial numbers": - print(_("The entitlement server successfully removed these serial numbers:")) - else: - print(_("The entitlement server successfully removed these IDs:")) - for id_ in success: - print(" {id_}".format(id_=id_)) - if failure: - if id_name == "pools": - print(_("The entitlement server failed to remove these pools:")) - elif id_name == "serial numbers": - print(_("The entitlement server failed to remove these serial numbers:")) - else: - print(_("The entitlement server failed to remove these IDs:")) - for id_ in failure: - print(" {id_}".format(id_=id_)) - - def _do_command(self): - """ - Executes the command. - """ - self._validate_options() - return_code = 0 - if self.is_registered(): - ent_service = entitlement.EntitlementService(self.cp) - try: - if self.options.all: - total = ent_service.remove_all_entitlements() - # total will be None on older Candlepins that don't - # support returning the number of subscriptions unsubscribed from - if total is None: - print(_("All subscriptions have been removed at the server.")) - else: - count = total["deletedRecords"] - print( - ungettext( - "%s subscription removed at the server.", - "%s subscriptions removed at the server.", - count, - ) - % count - ) - else: - # Try to remove subscriptions defined by pool IDs first (remove --pool=...) - if self.options.pool_ids: - ( - removed_pools, - unremoved_pools, - removed_serials, - ) = ent_service.remove_entitlements_by_pool_ids(self.options.pool_ids) - if not removed_pools: - return_code = 1 - self._print_unbind_ids_result(removed_pools, unremoved_pools, "pools") - else: - removed_serials = [] - # Then try to remove subscriptions defined by serials (remove --serial=...) - unremoved_serials = [] - if self.options.serials: - serials = unique_list_items(self.options.serials) - # Don't remove serials already removed by a pool - serials_to_remove = [serial for serial in serials if serial not in removed_serials] - _removed_serials, unremoved_serials = ent_service.remove_entitlements_by_serials( - serials_to_remove - ) - removed_serials.extend(_removed_serials) - if not _removed_serials: - return_code = 1 - # Print final result of removing pools - self._print_unbind_ids_result(removed_serials, unremoved_serials, "serial numbers") - except connection.GoneException as ge: - raise ge - except connection.RestlibException as err: - log.error(err) - - system_exit(os.EX_SOFTWARE, err) - except Exception as e: - handle_exception( - _("Unable to perform remove due to the following exception: {e}").format(e=e), e - ) - else: - # We never got registered, just remove the cert - try: - if self.options.all: - total = 0 - for ent in self.entitlement_dir.list(): - ent.delete() - total = total + 1 - print( - ungettext( - "{total} subscription removed from this system.", - "{total} subscriptions removed from this system.", - total, - ).format(total=total) - ) - else: - if self.options.serials or self.options.pool_ids: - serials = self.options.serials or [] - pool_ids = self.options.pool_ids or [] - count = 0 - for ent in self.entitlement_dir.list(): - ent_pool_id = str(getattr(ent.pool, "id", None) or "") - if str(ent.serial) in serials or ent_pool_id in pool_ids: - ent.delete() - print( - _( - "Subscription with serial number {serial} removed from this system" - ).format(serial=str(ent.serial)) - ) - count = count + 1 - if count == 0: - return_code = 1 - except Exception as e: - handle_exception( - _("Unable to perform remove due to the following exception: {e}").format(e=e), e - ) - - # it is okay to call this no matter what happens above, - # it's just a notification to perform a check - self._request_validity_check() - return return_code diff --git a/src/subscription_manager/cli_command/repos.py b/src/subscription_manager/cli_command/repos.py index fc87e5e0d0..d44f9045fc 100644 --- a/src/subscription_manager/cli_command/repos.py +++ b/src/subscription_manager/cli_command/repos.py @@ -19,18 +19,24 @@ import logging import subscription_manager.injection as inj +from rhsm.repofile import manage_repos_enabled, YumRepoFile from subscription_manager.action_client import ProfileActionClient, ActionClient from subscription_manager.cli_command.cli import CliCommand -from subscription_manager.cli_command.list import REPOS_LIST from subscription_manager.i18n import ugettext as _ from subscription_manager.packageprofilelib import PackageProfileActionInvoker from subscription_manager.printing_utils import columnize, echo_columnize_callback -from subscription_manager.repofile import manage_repos_enabled, YumRepoFile from subscription_manager.repolib import RepoActionInvoker from subscription_manager.utils import get_supported_resources log = logging.getLogger(__name__) +REPOS_LIST = [ + _("Repo ID:"), + _("Repo Name:"), + _("Repo URL:"), + _("Enabled:"), +] + class ReposAddRemoveAction(argparse.Action): """ diff --git a/src/subscription_manager/cli_command/service_level.py b/src/subscription_manager/cli_command/service_level.py index 917e56fce3..6f0b09595b 100644 --- a/src/subscription_manager/cli_command/service_level.py +++ b/src/subscription_manager/cli_command/service_level.py @@ -71,7 +71,7 @@ def _validate_options(self): if not self.is_registered(): if self.options.list: - if not (self.options.username and self.options.password) and not self.options.token: + if not (self.options.username and self.options.password): system_exit( os.EX_USAGE, _( @@ -89,14 +89,13 @@ def _validate_options(self): if self.is_registered() and ( getattr(self.options, "username", None) or getattr(self.options, "password", None) - or getattr(self.options, "token", None) or getattr(self.options, "org", None) or getattr(self.options, "server_url", None) ): system_exit( os.EX_USAGE, _( - "Error: --username, --password, --token, --org and --serverurl " + "Error: --username, --password, --org and --serverurl " "can be used only on unregistered systems" ), ) @@ -107,9 +106,7 @@ def _do_command(self): # If we have a username/password, we're going to use that, otherwise # we'll use the identity certificate. We already know one or the other # exists: - if self.options.token: - self.cp = self.cp_provider.get_keycloak_auth_cp(self.options.token) - elif self.options.username and self.options.password: + if self.options.username and self.options.password: self.cp_provider.set_user_pass(self.username, self.password) self.cp = self.cp_provider.get_basic_auth_cp() elif not self.is_registered() and self.options.show: diff --git a/src/subscription_manager/cli_command/syspurpose.py b/src/subscription_manager/cli_command/syspurpose.py index b466664b88..b5ae159fb9 100644 --- a/src/subscription_manager/cli_command/syspurpose.py +++ b/src/subscription_manager/cli_command/syspurpose.py @@ -22,7 +22,6 @@ from rhsm import connection from subscription_manager import syspurposelib -from subscription_manager.cli_command.addons import AddonsCommand from subscription_manager.cli_command.cli import CliCommand from subscription_manager.cli_command.role import RoleCommand from subscription_manager.cli_command.service_level import ServiceLevelCommand @@ -60,7 +59,7 @@ def __init__(self): # all the subcommands of 'syspurpose'; add them to this list to be # registered as such - syspurpose_command_classes = [AddonsCommand, RoleCommand, ServiceLevelCommand, UsageCommand] + syspurpose_command_classes = [RoleCommand, ServiceLevelCommand, UsageCommand] # create a subparser for all the subcommands: it is passed to all # the subcommand classes, so they will create an ArgumentParser that # is a child of the 'syspurpose' one, rather than as standalone diff --git a/src/subscription_manager/cli_command/user_pass.py b/src/subscription_manager/cli_command/user_pass.py index b5cb873247..1d760dbe00 100644 --- a/src/subscription_manager/cli_command/user_pass.py +++ b/src/subscription_manager/cli_command/user_pass.py @@ -44,11 +44,6 @@ def __init__(self, name, shortdesc=None, primary=False): dest="password", help=_("password to use when authorizing against the server"), ) - self.parser.add_argument( - "--token", - dest="token", - help=_("token to use when authorizing against the server"), - ) @staticmethod def _get_username_and_password(username, password): @@ -82,9 +77,6 @@ def _get_username_and_password(username, password): @property def username(self): if not self._username: - if self.options.token: - self._username = self.cp_provider.token_username - return self._username (self._username, self._password) = self._get_username_and_password( self.options.username, self.options.password ) @@ -92,7 +84,7 @@ def username(self): @property def password(self): - if not self._password and not self.options.token: + if not self._password: (self._username, self._password) = self._get_username_and_password( self.options.username, self.options.password ) diff --git a/src/subscription_manager/entcertlib.py b/src/subscription_manager/entcertlib.py index 84d3f0fc9d..c2ac33b516 100644 --- a/src/subscription_manager/entcertlib.py +++ b/src/subscription_manager/entcertlib.py @@ -108,9 +108,7 @@ def perform(self) -> "EntCertUpdateReport": log.info("certs updated:\n%s", self.report) self.syslog_results() - # We call EntCertlibActionInvoker.update() solo from - # the 'attach' cli instead of an ActionClient. So - # we need to refresh the ent_dir object before calling + # We need to refresh the ent_dir object before calling # content updating actions. self.ent_dir.refresh() diff --git a/src/subscription_manager/healinglib.py b/src/subscription_manager/healinglib.py deleted file mode 100644 index 737585a3f3..0000000000 --- a/src/subscription_manager/healinglib.py +++ /dev/null @@ -1,143 +0,0 @@ -# Copyright (c) 2013 Red Hat, Inc. -# -# This software is licensed to you under the GNU General Public License, -# version 2 (GPLv2). There is NO WARRANTY for this software, express or -# implied, including the implied warranties of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -# along with this software; if not, see -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -# -# Red Hat trademarks are not licensed under GPLv2. No permission is -# granted to use or replicate Red Hat trademarks that are incorporated -# in this software or its documentation. -# - -import datetime -import logging -from typing import List, TYPE_CHECKING - -from rhsm import certificate - -from subscription_manager import certlib -from subscription_manager import entcertlib -from subscription_manager import injection as inj - -if TYPE_CHECKING: - from subscription_manager.cert_sorter import CertSorter - from subscription_manager.identity import Identity - from subscription_manager.plugins import PluginManager - from rhsm.connection import UEPConnection - from subscription_manager.cp_provider import CPProvider - -log = logging.getLogger(__name__) - - -class HealingActionInvoker(certlib.BaseActionInvoker): - """ - An object used to run healing nightly. Checks cert validity for today, heals - if necessary, then checks for 24 hours from now, so we theoretically will - never have invalid certificates if subscriptions are available. - - NOTE: We may update entitlement status in this class, but we do not - update entitlement certs, since we are inside a lock. So a - EntCertActionInvoker.update() needs to follow a HealingActionInvoker.update() - """ - - def _do_update(self) -> int: - action = HealingUpdateAction() - return action.perform() - - -class HealingUpdateAction: - """UpdateAction for ent cert healing. - - Core if entitlement certificate healing. - - Asks RHSM API to calculate entitlement status today, and tomorrow. - If either show incomplete entitlement, ask the RHSM API to - auto attach pools to fix entitlement. - - Attempts to avoid gaps in entitlement coverage. - - Used by subscription-manager if the "autoheal" options - are enabled. - - Returns an EntCertUpdateReport with information about any ent - certs that were changed. - - Plugin hooks: - pre_auto_attach - post_auto_attach - """ - - def __init__(self): - self.cp_provider: CPProvider = inj.require(inj.CP_PROVIDER) - self.uep: UEPConnection = self.cp_provider.get_consumer_auth_cp() - self.report: entcertlib.EntCertUpdateReport = entcertlib.EntCertUpdateReport() - self.plugin_manager: PluginManager = inj.require(inj.PLUGIN_MANAGER) - - def perform(self): - # inject - identity: Identity = inj.require(inj.IDENTITY) - uuid: str = identity.uuid - consumer: dict = self.uep.getConsumer(uuid) - - if "autoheal" not in consumer or not consumer["autoheal"]: - log.warning("Auto-heal disabled on server, skipping.") - return 0 - - try: - today: datetime.datetime = datetime.datetime.now(certificate.GMT()) - tomorrow: datetime.datetime = today + datetime.timedelta(days=1) - valid_today: bool = False - valid_tomorrow: bool = False - - # Check if we're invalid today and heal if so. If we are - # valid, see if 24h from now is greater than our "valid until" - # date, and heal for tomorrow if so. - - cs: CertSorter = inj.require(inj.CERT_SORTER) - - cert_updater = entcertlib.EntCertActionInvoker() - if not cs.is_valid(): - log.warning("Found invalid entitlements for today: %s" % today) - self.plugin_manager.run("pre_auto_attach", consumer_uuid=uuid) - ents: List[dict] = self.uep.bind(uuid, today) - self.plugin_manager.run("post_auto_attach", consumer_uuid=uuid, entitlement_data=ents) - - # NOTE: we need to call EntCertActionInvoker.update after Healing.update - # otherwise, the locking get's crazy - # hmm, we use RLock, maybe we could use it here - self.report = cert_updater.update() - else: - valid_today = True - - if cs.compliant_until is None: - # Edge case here, not even sure this can happen as we - # should have a compliant until date if we're valid - # today, but just in case: - log.warning("Got valid status from server but no valid until date.") - elif tomorrow > cs.compliant_until: - log.warning("Entitlements will be invalid by tomorrow: %s" % tomorrow) - self.plugin_manager.run("pre_auto_attach", consumer_uuid=uuid) - ents = self.uep.bind(uuid, tomorrow) - self.plugin_manager.run("post_auto_attach", consumer_uuid=uuid, entitlement_data=ents) - self.report = cert_updater.update() - else: - valid_tomorrow = True - - msg = "Entitlement auto healing was checked and entitlements" - if valid_today: - msg += " are valid today %s" % today - if valid_tomorrow: - msg += " and tomorrow %s" % tomorrow - log.debug(msg) - - except Exception as e: - log.error("Error attempting to auto-heal:") - log.exception(e) - self.report._exceptions.append(e) - return self.report - else: - log.debug("Auto-heal check complete.") - return self.report diff --git a/src/subscription_manager/identity.py b/src/subscription_manager/identity.py index 1473c8dc5c..7b59dc6bbf 100644 --- a/src/subscription_manager/identity.py +++ b/src/subscription_manager/identity.py @@ -14,6 +14,7 @@ import logging import os +import grp import errno import threading from typing import Optional, TYPE_CHECKING @@ -108,14 +109,33 @@ def getConsumerOwner(self) -> Optional[str]: # TODO: we're using a Certificate which has it's own write/delete, no idea # why this landed in a parallel disjoint class wrapping the actual cert. def write(self) -> None: + """ + Write consumer key and certificate to disk. + """ from subscription_manager import managerlib + rhsm_group = None + try: + rhsm_group = grp.getgrnam(managerlib.RHSM_GROUP_NAME) + except KeyError: + log.error(f"Unable to get information about {managerlib.RHSM_GROUP_NAME}") + self.__mkdir() + with open(self.keypath(), "w") as key_file: key_file.write(self.key) + + # Set proper access permission to the key + if os.getuid() == 0 and rhsm_group is not None: + os.chown(self.keypath(), 0, rhsm_group.gr_gid) os.chmod(self.keypath(), managerlib.ID_CERT_PERMS) + with open(self.certpath(), "w") as cert_file: cert_file.write(self.cert) + + # Set proper permission to consumer certificate + if os.getuid() == 0 and rhsm_group is not None: + os.chown(self.certpath(), 0, rhsm_group.gr_gid) os.chmod(self.certpath(), managerlib.ID_CERT_PERMS) def delete(self) -> None: diff --git a/src/subscription_manager/injection.py b/src/subscription_manager/injection.py index 1c4487bde3..4f2f09b500 100644 --- a/src/subscription_manager/injection.py +++ b/src/subscription_manager/injection.py @@ -19,7 +19,6 @@ PRODUCT_DATE_RANGE_CALCULATOR = "PRODUCT_DATE_RANGE_CALCULATOR" ENT_DIR = "ENT_DIR" PROD_DIR = "PROD_DIR" -CONTENT_ACCESS_MODE_CACHE = "CONTENT_ACCESS_MODE_CACHE" CURRENT_OWNER_CACHE = "CURRENT_OWNER_CACHE" SYSPURPOSE_VALID_FIELDS_CACHE = "SYSPURPOSE_VALID_FIELDS_CACHE" SUPPORTED_RESOURCES_CACHE = "SUPPORTED_RESOURCES_CACHE" diff --git a/src/subscription_manager/injectioninit.py b/src/subscription_manager/injectioninit.py index 463ccb574c..8a181d995f 100644 --- a/src/subscription_manager/injectioninit.py +++ b/src/subscription_manager/injectioninit.py @@ -30,7 +30,6 @@ AvailableEntitlementsCache, CurrentOwnerCache, SyspurposeValidFieldsCache, - ContentAccessModeCache, ) from subscription_manager.cert_sorter import CertSorter @@ -65,7 +64,6 @@ def init_dep_injection(): # but runs a new version of injectioninit...) inj.provide(inj.ENTITLEMENT_STATUS_CACHE, EntitlementStatusCache, singleton=True) inj.provide(inj.SYSTEMPURPOSE_COMPLIANCE_STATUS_CACHE, SyspurposeComplianceStatusCache, singleton=True) - inj.provide(inj.CONTENT_ACCESS_MODE_CACHE, ContentAccessModeCache, singleton=True) inj.provide(inj.CURRENT_OWNER_CACHE, CurrentOwnerCache, singleton=True) inj.provide(inj.SYSPURPOSE_VALID_FIELDS_CACHE, SyspurposeValidFieldsCache) inj.provide(inj.SUPPORTED_RESOURCES_CACHE, SupportedResourcesCache, singleton=True) diff --git a/src/subscription_manager/managercli.py b/src/subscription_manager/managercli.py index ab8a36c12a..b6441457b6 100644 --- a/src/subscription_manager/managercli.py +++ b/src/subscription_manager/managercli.py @@ -23,8 +23,6 @@ from subscription_manager import managerlib from subscription_manager.cli import CLI -from subscription_manager.cli_command.attach import AttachCommand -from subscription_manager.cli_command.autoheal import AutohealCommand from subscription_manager.cli_command.clean import CleanCommand from subscription_manager.cli_command.config import ConfigCommand from subscription_manager.cli_command.environments import EnvironmentsCommand @@ -34,11 +32,9 @@ from subscription_manager.cli_command.override import OverrideCommand from subscription_manager.cli_command.owners import OwnersCommand from subscription_manager.cli_command.plugins import PluginsCommand -from subscription_manager.cli_command.redeem import RedeemCommand from subscription_manager.cli_command.refresh import RefreshCommand from subscription_manager.cli_command.register import RegisterCommand from subscription_manager.cli_command.release import ReleaseCommand -from subscription_manager.cli_command.remove import RemoveCommand from subscription_manager.cli_command.repos import ReposCommand from subscription_manager.cli_command.status import StatusCommand from subscription_manager.cli_command.syspurpose import SyspurposeCommand @@ -61,16 +57,12 @@ def __init__(self): OwnersCommand, RefreshCommand, CleanCommand, - RedeemCommand, ReposCommand, ReleaseCommand, StatusCommand, EnvironmentsCommand, VersionCommand, - RemoveCommand, - AttachCommand, PluginsCommand, - AutohealCommand, OverrideCommand, FactsCommand, SyspurposeCommand, diff --git a/src/subscription_manager/managerlib.py b/src/subscription_manager/managerlib.py index b61755f72e..e291ddc9e3 100644 --- a/src/subscription_manager/managerlib.py +++ b/src/subscription_manager/managerlib.py @@ -17,7 +17,7 @@ import glob import logging import os -import re +import grp import shutil import stat import syslog @@ -25,7 +25,6 @@ from rhsm.config import get_config_parser -from rhsm.certificate import Key, CertificateException, create_from_pem import subscription_manager.cache as cache from subscription_manager.cert_sorter import StackingGroupSorter, ComplianceManager @@ -75,6 +74,7 @@ # Expected permissions for identity certificates: ID_CERT_PERMS: int = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP +RHSM_GROUP_NAME: str = "rhsm" def system_log(message: str, priority: int = syslog.LOG_NOTICE) -> None: @@ -747,166 +747,6 @@ def lookup_provided_products(self, pool_id: str) -> Optional[List[Tuple[str, str return provided_products -class ImportFileExtractor: - """ - Responsible for checking an import file and pulling cert and key from it. - An import file may include only the certificate, but may also include its - key. - - An import file is processed looking for: - - -----BEGIN ----- - - .. - -----END ----- - - and will only process if it finds CERTIFICATE or KEY in the text. - - For example the following would locate a key and cert. - - -----BEGIN CERTIFICATE----- - - -----END CERTIFICATE----- - -----BEGIN PUBLIC KEY----- - - -----END PUBLIC KEY----- - - """ - - _REGEX_START_GROUP = "start" - _REGEX_CONTENT_GROUP = "content" - _REGEX_END_GROUP = "end" - _REGEX = r"(?P<%s>[-]*BEGIN[\w\ ]*[-]*)(?P<%s>[^-]*)(?P<%s>[-]*END[\w\ ]*[-]*)" % ( - _REGEX_START_GROUP, - _REGEX_CONTENT_GROUP, - _REGEX_END_GROUP, - ) - _PATTERN = re.compile(_REGEX) - - _CERT_DICT_TAG = "CERTIFICATE" - _KEY_DICT_TAG = "KEY" - _ENT_DICT_TAG = "ENTITLEMENT" - _SIG_DICT_TAG = "RSA SIGNATURE" - - def __init__(self, cert_file_path: str): - self.path = cert_file_path - self.file_name = os.path.basename(cert_file_path) - - content = self._read(cert_file_path) - self.parts = self._process_content(content) - - def _read(self, file_path: str) -> str: - fd = open(file_path, "r") - file_content = fd.read() - fd.close() - return file_content - - def _process_content(self, content: str) -> Dict[str, str]: - part_dict = {} - matches = self._PATTERN.finditer(content) - for match in matches: - start = match.group(self._REGEX_START_GROUP) - meat = match.group(self._REGEX_CONTENT_GROUP) - end = match.group(self._REGEX_END_GROUP) - - dict_key = None - if not start.find(self._KEY_DICT_TAG) < 0: - dict_key = self._KEY_DICT_TAG - elif not start.find(self._CERT_DICT_TAG) < 0: - dict_key = self._CERT_DICT_TAG - elif not start.find(self._ENT_DICT_TAG) < 0: - dict_key = self._ENT_DICT_TAG - elif not start.find(self._SIG_DICT_TAG) < 0: - dict_key = self._SIG_DICT_TAG - - if dict_key is None: - continue - - part_dict[dict_key] = start + meat + end - return part_dict - - def contains_key_content(self) -> bool: - return self._KEY_DICT_TAG in self.parts - - def get_key_content(self) -> Optional[str]: - key_content = None - if self._KEY_DICT_TAG in self.parts: - key_content = self.parts[self._KEY_DICT_TAG] - return key_content - - def get_cert_content(self) -> str: - cert_content = "" - if self._CERT_DICT_TAG in self.parts: - cert_content = self.parts[self._CERT_DICT_TAG] - if self._ENT_DICT_TAG in self.parts: - cert_content = cert_content + os.linesep + self.parts[self._ENT_DICT_TAG] - if self._SIG_DICT_TAG in self.parts: - cert_content = cert_content + os.linesep + self.parts[self._SIG_DICT_TAG] - return cert_content - - def verify_valid_entitlement(self) -> bool: - """ - Verify that a valid entitlement was processed. - - @return: True if valid, False otherwise. - """ - try: - cert = self.get_cert() - # Don't want to check class explicitly, instead we'll look for - # order info, which only an entitlement cert could have: - if not hasattr(cert, "order"): - return False - except CertificateException: - return False - ent_key = Key(self.get_key_content()) - if ent_key.bogus(): - return False - return True - - # TODO: rewrite to use certlib.EntitlementCertBundleInstall? - def write_to_disk(self) -> None: - """ - Write/copy cert to the entitlement cert dir. - """ - self._ensure_entitlement_dir_exists() - dest_file_path = os.path.join(ENT_CONFIG_DIR, self._create_filename_from_cert_serial_number()) - - # Write the key/cert content to new files - log.debug("Writing certificate file: %s" % (dest_file_path)) - cert_content = self.get_cert_content() - self._write_file(dest_file_path, cert_content) - - if self.contains_key_content(): - dest_key_file_path = self._get_key_path_from_dest_cert_path(dest_file_path) - log.debug("Writing key file: %s" % (dest_key_file_path)) - self._write_file(dest_key_file_path, self.get_key_content()) - - def _write_file(self, target_path: str, content: str) -> None: - new_file = open(target_path, "w") - try: - new_file.write(content) - finally: - new_file.close() - - def _ensure_entitlement_dir_exists(self) -> None: - if not os.access(ENT_CONFIG_DIR, os.R_OK): - os.mkdir(ENT_CONFIG_DIR) - - def _get_key_path_from_dest_cert_path(self, dest_cert_path: str) -> str: - file_parts = os.path.splitext(dest_cert_path) - return file_parts[0] + "-key" + file_parts[1] - - def _create_filename_from_cert_serial_number(self) -> str: - "create from serial" - ent_cert = self.get_cert() - return "%s.pem" % (ent_cert.serial) - - def get_cert(self) -> "EntitlementCertificate": - cert_content: str = self.get_cert_content() - ent_cert: EntitlementCertificate = create_from_pem(cert_content) - return ent_cert - - def _sub_dict(datadict: dict, subkeys: Iterable[str], default: Optional[object] = None) -> dict: """Return a dict that is a subset of datadict matching only the keys in subkeys""" return dict([(k, datadict.get(k, default)) for k in subkeys]) @@ -934,6 +774,18 @@ def format_iso8601_date(dateobj: Optional[datetime.datetime]) -> str: return "" +def get_rhsm_group() -> Optional[grp.struct_group]: + """ + Try to get GUID about rhsm group + """ + rhsm_group = None + try: + rhsm_group = grp.getgrnam(RHSM_GROUP_NAME) + except KeyError: + log.error(f"Unable to get information about {RHSM_GROUP_NAME}") + return rhsm_group + + # FIXME: move me to identity.py def check_identity_cert_perms() -> None: """ @@ -941,19 +793,23 @@ def check_identity_cert_perms() -> None: fix them if not. """ certs: List[str] = [identity.ConsumerIdentity.keypath(), identity.ConsumerIdentity.certpath()] + rhsm_group = get_rhsm_group() + cert_guid = 0 + if rhsm_group is not None: + cert_guid = rhsm_group.gr_gid for cert in certs: if not os.path.exists(cert): # Only relevant if these files exist. continue statinfo: os.stat_result = os.stat(cert) - if statinfo[stat.ST_UID] != 0 or statinfo[stat.ST_GID] != 0: - os.chown(cert, 0, 0) - log.warn("Corrected incorrect ownership of %s." % cert) + if statinfo[stat.ST_UID] != 0 or statinfo[stat.ST_GID] != cert_guid: + os.chown(cert, 0, cert_guid) + log.warning("Corrected incorrect ownership of %s." % cert) mode: int = stat.S_IMODE(statinfo[stat.ST_MODE]) if mode != ID_CERT_PERMS: os.chmod(cert, ID_CERT_PERMS) - log.warn("Corrected incorrect permissions on %s." % cert) + log.warning("Corrected incorrect permissions on %s." % cert) def clean_all_data(backup: bool = True) -> None: diff --git a/src/subscription_manager/plugins.py b/src/subscription_manager/plugins.py index 650539100c..947464b23b 100644 --- a/src/subscription_manager/plugins.py +++ b/src/subscription_manager/plugins.py @@ -379,7 +379,6 @@ def __init__(self, clazz: Type[SubManPlugin], consumer_uuid: str, pool_id: str, consumer_uuid: the UUID of the consumer being subscribed pool_id: the id of the pool the subscription will come from (None if 'auto' is False) quantity: the quantity to consume from the pool (None if 'auto' is False). - auto: is this an auto-attach/healing event. """ super(SubscriptionConduit, self).__init__(clazz) self.consumer_uuid: str = consumer_uuid @@ -402,33 +401,6 @@ def __init__(self, clazz: Type[SubManPlugin], consumer_uuid: str, entitlement_da self.entitlement_data: Dict = entitlement_data -class AutoAttachConduit(BaseConduit): - slots = ["pre_auto_attach"] - - def __init__(self, clazz: Type[SubManPlugin], consumer_uuid: str): - """ - init for AutoAttachConduit - - Args: - consumer_uuid: the UUID of the consumer being auto-subscribed - """ - super(AutoAttachConduit, self).__init__(clazz) - self.consumer_uuid: str = consumer_uuid - - -class PostAutoAttachConduit(PostSubscriptionConduit): - slots = ["post_auto_attach"] - - def __init__(self, clazz: Type[SubManPlugin], consumer_uuid: str, entitlement_data: Dict): - """init for PostAutoAttachConduit - - Args: - consumer_uuid: the UUID of the consumer subscribed - entitlement_data: the data returned by the server - """ - super(PostAutoAttachConduit, self).__init__(clazz, consumer_uuid, entitlement_data) - - class PluginConfig: """Represents configuation for each rhsm plugin. @@ -917,8 +889,6 @@ def _get_conduits(self) -> List[type(BaseConduit)]: SubscriptionConduit, UpdateContentConduit, PostSubscriptionConduit, - AutoAttachConduit, - PostAutoAttachConduit, ] def _get_modules(self): diff --git a/src/subscription_manager/repolib.py b/src/subscription_manager/repolib.py index 6fdf375b1d..b6a2503f21 100644 --- a/src/subscription_manager/repolib.py +++ b/src/subscription_manager/repolib.py @@ -22,14 +22,20 @@ from subscription_manager.cache import OverrideStatusCache, WrittenOverrideCache from subscription_manager import model from subscription_manager.model import ent_cert -from subscription_manager.repofile import Repo, manage_repos_enabled, get_repo_file_classes -from subscription_manager.repofile import YumRepoFile from subscription_manager.utils import get_supported_resources +from rhsm.repofile import Repo, manage_repos_enabled, get_repo_file_classes +from rhsm.repofile import YumRepoFile, RepoFileBase +from rhsm.repofile import HAS_DEB822, HAS_ZYPP import rhsm.config import configparser from rhsmlib.facts.hwprobe import HardwareCollector +if HAS_DEB822: + from rhsm.repofile import AptRepoFile +if HAS_ZYPP: + from rhsm.repofile import ZypperRepoFile + # FIXME: local imports from subscription_manager.certlib import ActionReport, BaseActionInvoker @@ -217,12 +223,12 @@ def get_repos(self, apply_overrides: bool = True) -> Set[Repo]: current = set() # Add the current repo data - yum_repo_file = YumRepoFile() - yum_repo_file.read() + repo_file = self.get_system_repo_file() + repo_file.read() server_value_repo_file = YumRepoFile("var/lib/rhsm/repo_server_val/") server_value_repo_file.read() for repo in repos: - existing = yum_repo_file.section(repo.id) + existing = repo_file.section(repo.id) server_value_repo = server_value_repo_file.section(repo.id) # we need a repo in the server val file to match any in # the main repo definition file @@ -237,9 +243,18 @@ def get_repos(self, apply_overrides: bool = True) -> Set[Repo]: return current + def get_system_repo_file(self) -> "RepoFileBase": + if HAS_DEB822: + repo = AptRepoFile() + elif HAS_ZYPP: + repo = ZypperRepoFile() + else: + repo = YumRepoFile() + return repo + def get_repo_file(self) -> str: - yum_repo_file = YumRepoFile() - return yum_repo_file.path + repo_file = self.get_system_repo_file() + return repo_file.path @classmethod def delete_repo_file(cls) -> None: diff --git a/src/subscription_manager/scripts/rhsm_service.py b/src/subscription_manager/scripts/rhsm_service.py index 938c5dcb93..3ec839b6b5 100755 --- a/src/subscription_manager/scripts/rhsm_service.py +++ b/src/subscription_manager/scripts/rhsm_service.py @@ -33,7 +33,6 @@ def main(): object_classes = [ objects.ConfigDBusObject, objects.RegisterDBusObject, - objects.AttachDBusObject, objects.ProductsDBusObject, objects.UnregisterDBusObject, objects.EntitlementDBusObject, diff --git a/src/subscription_manager/scripts/rhsmcertd_worker.py b/src/subscription_manager/scripts/rhsmcertd_worker.py index 3a56e0380c..925bd0cd39 100644 --- a/src/subscription_manager/scripts/rhsmcertd_worker.py +++ b/src/subscription_manager/scripts/rhsmcertd_worker.py @@ -31,7 +31,7 @@ from subscription_manager import cache from subscription_manager import entcertlib from subscription_manager import managerlib -from subscription_manager.action_client import HealingActionClient, ActionClient +from subscription_manager.action_client import ActionClient from subscription_manager.i18n import ugettext as _ from subscription_manager.i18n_argparse import ArgumentParser, USAGE from subscription_manager.identity import Identity, ConsumerIdentity @@ -160,6 +160,25 @@ def _collect_cloud_info(cloud_list: List[str]) -> dict: return result +def _auto_register_wait() -> None: + """Delay during the automatic registration. + + Wait for an amount of time during automatic registration, looking at the + configured splay and autoregistration interval. + """ + cfg = config.get_config_parser() + if cfg.get("rhsmcertd", "splay") == "0": + log.debug("Trying to obtain the identity immediately, splay is disabled.") + else: + registration_interval = int(cfg.get("rhsmcertd", "auto_registration_interval")) + splay_interval: int = random.randint(60, registration_interval * 60) + log.debug( + f"Waiting a period of {splay_interval} seconds " + f"(about {splay_interval // 60} minutes) before attempting to obtain the identity." + ) + time.sleep(splay_interval) + + def _auto_register(cp_provider: "CPProvider") -> ExitStatus: """Try to perform automatic registration. @@ -191,7 +210,7 @@ def _auto_register(cp_provider: "CPProvider") -> ExitStatus: # Obtain automatic registration token try: - token: Dict[str, str] = cache.CloudTokenCache.get( + token: Dict[str, str] = cache.CloudTokenCache._get_from_server( uep=uep, cloud_id=cloud_info["cloud_id"], metadata=cloud_info["metadata"], @@ -201,29 +220,14 @@ def _auto_register(cp_provider: "CPProvider") -> ExitStatus: log.exception("Cloud token could not be obtained. Unable to perform automatic registration.") return ExitStatus.NO_REGISTRATION_TOKEN - if token["tokenType"] == "CP-Cloud-Registration": - try: - _auto_register_standard(uep=uep, token=token) - except Exception: - log.exception("Standard automatic registration failed.") - return ExitStatus.REGISTRATION_FAILED - else: - log.info("Standard automatic registration was successful.") - return ExitStatus.OK - - if token["tokenType"] == "CP-Anonymous-Cloud-Registration": - try: - _auto_register_anonymous(uep=uep, token=token) - cache.CloudTokenCache.delete_cache() - except Exception: - log.exception("Anonymous automatic registration failed.") - return ExitStatus.REGISTRATION_FAILED - else: - log.info("Anonymous automatic registration was successful.") - return ExitStatus.OK - - log.error(f"Unsupported token type for automatic registration: {token['tokenType']}.") - return ExitStatus.BAD_TOKEN_TYPE + try: + _auto_register_standard(uep=uep, token=token) + except Exception: + log.exception("Standard automatic registration failed.") + return ExitStatus.REGISTRATION_FAILED + else: + log.info("Standard automatic registration was successful.") + return ExitStatus.OK def _auto_register_standard(uep: "UEPConnection", token: Dict[str, str]) -> None: @@ -235,8 +239,10 @@ def _auto_register_standard(uep: "UEPConnection", token: Dict[str, str]) -> None """ log.debug("Registering the system through standard automatic registration.") + _auto_register_wait() + service = RegisterService(cp=uep) - service.register(org=None, jwt_token=token["token"]) + service.register(org=None, jwt_token=token) def _auto_register_anonymous(uep: "UEPConnection", token: Dict[str, str]) -> None: @@ -260,17 +266,7 @@ def _auto_register_anonymous(uep: "UEPConnection", token: Dict[str, str]) -> Non manager.install_temporary_certificates(uuid=token["anonymousConsumerUuid"], jwt=token["token"]) # Step 2: Wait - cfg = config.get_config_parser() - if cfg.get("rhsmcertd", "splay") == "0": - log.debug("Trying to obtain the identity immediately, splay is disabled.") - else: - registration_interval = int(cfg.get("rhsmcertd", "auto_registration_interval")) - splay_interval: int = random.randint(60, registration_interval * 60) - log.debug( - f"Waiting a period of {splay_interval} seconds " - f"(about {splay_interval // 60} minutes) before attempting to obtain the identity." - ) - time.sleep(splay_interval) + _auto_register_wait() # Step 3: Obtain the identity certificate log.debug("Obtaining system identity") @@ -292,15 +288,20 @@ def _auto_register_anonymous(uep: "UEPConnection", token: Dict[str, str]) -> Non log.debug(report) return except connection.RateLimitExceededException as exc: - if exc.headers.get("Retry-After", None) is None: + if exc.retry_after is None: + log.warning( + "Server did not include Retry-After header in rate-limited response. " + f"headers={exc.headers}" + ) raise - delay = int(exc.headers["Retry-After"]) + delay = exc.retry_after log.debug( f"Got response with status code {exc.code} and Retry-After header, " f"will try again in {delay} seconds." ) time.sleep(delay) - except Exception: + except Exception as exc: + log.warning(f"Anonymous registration failed, server returned {exc}.") raise # In theory, this should not happen, it means that something has gone wrong server-side. @@ -345,11 +346,7 @@ def _main(args: "argparse.Namespace"): uep.supports_resource(None) try: - if args.autoheal: - action_client = HealingActionClient() - else: - action_client = ActionClient() - + action_client = ActionClient() action_client.update() for update_report in action_client.update_reports: @@ -381,13 +378,6 @@ def main(): logutil.init_logger() parser = ArgumentParser(usage=USAGE) - parser.add_argument( - "--autoheal", - dest="autoheal", - action="store_true", - default=False, - help="perform an autoheal check", - ) parser.add_argument("--force", dest="force", action="store_true", default=False, help=SUPPRESS) parser.add_argument( "--auto-register", diff --git a/src/subscription_manager/syspurposelib.py b/src/subscription_manager/syspurposelib.py index d26482fc05..d147fbd1f3 100644 --- a/src/subscription_manager/syspurposelib.py +++ b/src/subscription_manager/syspurposelib.py @@ -42,31 +42,6 @@ syspurpose = None -def save_sla_to_syspurpose_metadata(uep: "UEPConnection", consumer_uuid: str, service_level: str): - """ - Saves the provided service-level value to the local Syspurpose Metadata (syspurpose.json) file. - If the service level provided is null or empty, the sla value to the local syspurpose file is set to null. - when uep and consumer_uuid is not None, then service_level is also synced with candlepin server - - :param uep: The object with uep connection (connection to candlepin server) - :param consumer_uuid: Consumer UUID - :param service_level: The service-level value to be saved in the syspurpose file. - """ - - if "SyncedStore" in globals() and SyncedStore is not None: - synced_store = SyncedStore(uep=uep, consumer_uuid=consumer_uuid) - - # if empty, set it to null - if service_level is None or service_level == "": - service_level = None - - synced_store.set("service_level_agreement", service_level) - synced_store.finish() - log.debug("Syspurpose SLA value successfully saved locally.") - else: - log.error("SyspurposeStore could not be imported. Syspurpose SLA value not saved locally.") - - def get_sys_purpose_store() -> Optional[SyncedStore]: """ :return: Returns a singleton instance of the syspurpose store if it was imported. @@ -118,7 +93,7 @@ def write_syspurpose(values: dict) -> bool: else: # Simple backup in case the syspurpose tooling is not installed. try: - json.dump(values, open(USER_SYSPURPOSE), ensure_ascii=True, indent=2) + json.dump(values, open(USER_SYSPURPOSE, "w"), ensure_ascii=True, indent=2) except OSError: log.warning("Could not write syspurpose to %s" % USER_SYSPURPOSE) return False diff --git a/src/subscription_manager/utils.py b/src/subscription_manager/utils.py index 31c6cbfab1..ce19fd97fe 100644 --- a/src/subscription_manager/utils.py +++ b/src/subscription_manager/utils.py @@ -24,7 +24,7 @@ import socket import syslog import uuid -from typing import Callable, Dict, Iterable, Iterator, Optional, Tuple, TYPE_CHECKING +from typing import Dict, Iterable, Iterator, Optional, Tuple, TYPE_CHECKING import urllib @@ -202,8 +202,7 @@ def is_simple_content_access( # When identity is not known, then system is not registered if identity.uuid is None: return False - content_access_mode = inj.require(inj.CONTENT_ACCESS_MODE_CACHE).read_data(uep=uep) - return content_access_mode == "org_environment" + return True def get_current_owner(uep: Optional["UEPConnection"] = None, identity: "Identity" = None) -> dict: @@ -571,24 +570,6 @@ def print_error(message: str) -> None: sys.stderr.write("\n") -def unique_list_items(items: Iterable, hash_function: Callable = lambda x: x) -> list: - """ - Accepts a list of items. - Returns a list of the unique items in the input. - Maintains order. - """ - observed = set() - unique_items = [] - for item in items: - item_key = hash_function(item) - if item_key in observed: - continue - else: - unique_items.append(item) - observed.add(item_key) - return unique_items - - def generate_correlation_id() -> str: return str(uuid.uuid4()).replace("-", "") # FIXME cp should accept - diff --git a/subscription-manager.spec b/subscription-manager.spec index 3c22f13736..1b43861062 100644 --- a/subscription-manager.spec +++ b/subscription-manager.spec @@ -95,7 +95,7 @@ %global exclude_packages %{exclude_packages}" Name: subscription-manager -Version: 1.29.40 +Version: 1.30.3 Release: 1%{?dist} Summary: Tools and libraries for subscription and repository management %if 0%{?suse_version} @@ -435,6 +435,9 @@ find %{buildroot} -name \*.py* -exec touch -r %{SOURCE0} '{}' \; ln -s %{_sbindir}/service %{buildroot}/%{_sbindir}/rcrhsmcertd %endif +# Remove script which we only need in debian/ubuntu +rm -f %{buildroot}%{_bindir}/package-profile-upload + # base/cli tools use the gettext domain 'rhsm', while the # gnome-help tools use domain 'subscription-manager' %files -f rhsm.lang @@ -527,6 +530,8 @@ find %{buildroot} -name \*.py* -exec touch -r %{SOURCE0} '{}' \; %{completion_dir}/rhsm-debug %{completion_dir}/rhsmcertd +%{_sysusersdir}/rhsm.conf + %dir %{python_sitearch}/subscription_manager # code, python modules and packages @@ -657,7 +662,8 @@ find %{buildroot} -name \*.py* -exec touch -r %{SOURCE0} '{}' \; %attr(750,root,root) %dir %{_var}/cache/cloud-what %dir %{python_sitearch}/cloud_what %dir %{python_sitearch}/cloud_what/providers -%{python_sitearch}/cloud_what/* +%{python_sitearch}/cloud_what/*.py* +%{python_sitearch}/cloud_what/providers/*.py* %{python_sitearch}/cloud_what/__pycache__ %{python_sitearch}/cloud_what/providers/__pycache__ @@ -689,6 +695,10 @@ if [ "$1" = "2" ] ; then fi %endif +# Make all consumer certificates and keys readable by group rhsm +find /etc/pki/consumer -mindepth 1 -maxdepth 1 -name '*.pem' | xargs --no-run-if-empty chgrp rhsm +find /etc/pki/consumer -mindepth 1 -maxdepth 1 -name '*.pem' | xargs --no-run-if-empty chmod g+r + # Make all entitlement certificates and keys files readable by group and other find /etc/pki/entitlement -mindepth 1 -maxdepth 1 -name '*.pem' | xargs --no-run-if-empty chmod go+r @@ -733,8 +743,148 @@ rmdir %{python_sitearch}/subscription_manager-*-*.egg-info --ignore-fail-on-non- # Remove old cache files # The -f flag ensures that exit code 0 will be returned even if the file does not exist. rm -f /var/lib/rhsm/cache/rhsm_icon.json +rm -f /var/lib/rhsm/cache/content_access_mode.json %changelog +* Thu Dec 19 2024 Jiri Hnidek 1.30.3-1 +- Card ID: CCT-731 - integration tests for DBus Register method + (jstavel@redhat.com) +- chore: Remove the --token authentication (pkoprda@redhat.com) +- chore: Remove artifacts of import (stomsa@redhat.com) +- chore: Remove artifacts of redeem (stomsa@redhat.com) +- chore: Remove artifacts of remove (stomsa@redhat.com) +- chore: Remove artifacts of autoheal (glutexo@icloud.com) +- chore: Remove artifacts of --auto-attach (glutexo@icloud.com) +- chore: Remove artifacts of attach (stomsa@redhat.com) +- fix: Added missing python packages (jhnidek@redhat.com) +- fix: Fixed integration tests (jhnidek@redhat.com) +- chore: Remove auto-assign CI job (mhorky@redhat.com) +- feat: Add initial support for tmt (jhnidek@redhat.com) +- fix: Renamed integration-tests to cockpit-tests (jhnidek@redhat.com) +- Feat CCT-965: Include timezone in the logs In `subscription- + manager/src/rhsm/logutil.py`i: (mgrunwal@redhat.com) +- feat: remove content access mode cache (jajerome@redhat.com) +- feat: add GetEnvironments method to DBus register (ryanverdile@gmail.com) +- feat: Added basic configuration for Packit (jhnidek@redhat.com) +- fix: drop "autoattachinterval" from the rhsmcertd defaults + (ptoscano@redhat.com) +- feat/cct-875: Options -i and --cert-interval were removed from rhsmcertd + command autocompletion Options -i and --cert-interval were removed from + rhsmcertd command autocompletion. (mgrunwal@mgrunwal- + thinkpadp1gen3.rmtcz.csb) +- feat/cct-874: Autocompletion for option --servicelevel removed Autocompletion + for option --servicelevel (sub-man register --servicelevel) was removed. + (mgrunwal@mgrunwal-thinkpadp1gen3.rmtcz.csb) +- fix: Handle Retry-After headers better for 429 responses (mhorky@redhat.com) +- feat: Better automatic registration logging (mhorky@redhat.com) +- refactor: Moved some definition of lists from list.py (jhnidek@redhat.com) +- fix: Removed show_autosubscribe_output() method (jhnidek@redhat.com) +- feat: Remove useless CLI options from list command (jhnidek@redhat.com) +- fix: perform autoreg waiting when performing standard autoreg + (ptoscano@redhat.com) +- chore: move autoreg waiting code in own function (ptoscano@redhat.com) +- feat: support registering specifying environments with activation keys + (ryanverdile@gmail.com) +- feat: Remove autoheal functionality from rhsmcertd (jvlcek@redhat.com) + +* Thu Sep 26 2024 Pino Toscano 1.30.2-1 +- Translated using Weblate (Georgian) (temuri.doghonadze@gmail.com) +- feat: Create consumer cert & key owner by rhsm group (jhnidek@redhat.com) +- feat: Add rhsm group during installation of subman RPM (jhnidek@redhat.com) +- feat: dnf plugin - outsource uploading of profile to rhsmcertd. + (jhnidek@redhat.com) +- docs: remove references to removed commands (jajerome@redhat.com) +- feat: Remove auto-attach command (jhnidek@redhat.com) +- feat: Eliminate command 'remove' from subscription-manager + (jvlcek@redhat.com) +- feat: Remove attach from bash completion script (jhnidek@redhat.com) +- feat: Remove references on auto-attach in man page (jhnidek@redhat.com) +- feat: Removed attach service (jhnidek@redhat.com) +- feat: Removed D-Bus methods related to attach (jhnidek@redhat.com) +- feat: Removed attach command and CLI option related to attach + (jhnidek@redhat.com) +- feat: Remove 'addons' subcommand(s) (mhorky@redhat.com) +- feat: Removed command "redeem" from subscription-manager (jhnidek@redhat.com) +- Update the correct man page file. (jvlcek@redhat.com) +- docs: Change reverse proxy to proxy in man page (jvlcek@redhat.com) +- test(ci): Improve container pre-test script (mhorky@redhat.com) + +* Wed Aug 21 2024 Pino Toscano 1.30.1-1 +- feat: forcefully switch automatic cloud registration to v1 + (ptoscano@redhat.com) + +* Fri Aug 16 2024 Pino Toscano 1.30.0-1 +- Translated using Weblate (Russian) (aleksejfedorov963@gmail.com) +- Translated using Weblate (Korean) (simmon@nplob.com) +- chore: Format register.py (mhorky@redhat.com) +- feat: Require SCA for registration (mhorky@redhat.com) +- doc: Update install and testing guide (stomsa@redhat.com) +- Fixed spec file to list packages twice (suttner@atix.de) +- code review comments fixes - update metadata and test. (chambrid@redhat.com) +- feat: Disable anonymous cloud registration temporarily (mhorky@redhat.com) +- Collect Azure VM Name and Resource Group Name as a cloud fact. + (chambrid@redhat.com) +- fix: Improve wording in redhat.repo template (glutexo@icloud.com) +- Remove commands moved to syspurpose (glutexo@icloud.com) +- doc: drop references to "activate" (ptoscano@redhat.com) +- feat: Remove import command (zpetrace@redhat.com) +- fix: make SyspurposeComplianceStatusCache.get_overall_status() always usable + (ptoscano@redhat.com) +- fix: Change order of checks (jhnidek@redhat.com) +- fix: Cache shouldn't try to get data from server without UUID + (jhnidek@redhat.com) +- feat: Add option to run smoke tests with fake IMDS servers. + (jhnidek@redhat.com) +- fix: Hide subscription management "errors" in container mode + (mhorky@redhat.com) +- feat(ci): Update testing matrix (mhorky@redhat.com) +- fix(test): Properly stop method mock (mhorky@redhat.com) +- feat: Azure: added extended location and type of location fact + (jhnidek@redhat.com) +- fix: Update version of Azure metadata (jhnidek@redhat.com) +- feat: Added Azure location to facts (jhnidek@redhat.com) +- feat: Added zone GCP fact (jhnidek@redhat.com) +- feat: Added more AWS cloud facts (jhnidek@redhat.com) +- fix: Change type hint according returned value. (jhnidek@redhat.com) +- feat: Add warning message about release version to dnf plugin + (jhnidek@redhat.com) +- Bump black from 23.3.0 to 24.3.0 + (49699333+dependabot[bot]@users.noreply.github.com) +- Format code with black==24.3.0 (ptoscano@redhat.com) +- Fix memory leaks in test-productdb.c (jhnidek@redhat.com) +- Fix memory leaks in productdb.c (jhnidek@redhat.com) +- fix: Function prototype without declaration is deprecated + (jhnidek@redhat.com) +- Removed unused includes of .h files (jhnidek@redhat.com) +- libdnf: switch from g_error_free() to g_clear_error() in tests + (ptoscano@redhat.com) +- libdnf: do not build test code in plugin (ptoscano@redhat.com) +- Change handling of deprecated `datetime.datetime.utcnow()` + (mhorky@redhat.com) +- CCT-66: Update identity reporting in DNF plugin during autoregistration + (mhorky@redhat.com) +- Remove automatic registration delay for rhsmcertd (mhorky@redhat.com) +- Remove API endpoint for automatic cloud registration v1 (mhorky@redhat.com) +- CCT-67: Use automatic registration v2 (mhorky@redhat.com) +- IdentityUpdateAction: Improve logging for updating identity certificates + (mhorky@redhat.com) +- Identity: Add method to extract current owner (mhorky@redhat.com) +- rhsmcertd: Define exit codes (mhorky@redhat.com) +- rhsmcertd: Use module-level logger (mhorky@redhat.com) +- Add AnonymousCertificateManager (mhorky@redhat.com) +- Add CloudTokenCache for Candlepin JWT (mhorky@redhat.com) +- Implement API endpoints for Automatic registration v2 (mhorky@redhat.com) +- Update documentation for one API call in connection.py (mhorky@redhat.com) +- Fix type hint of RegisterService.register() (mhorky@redhat.com) +- rhsmcertd: Drop D-Bus loop code (mhorky@redhat.com) +- rhsmcertd: Add type hints (mhorky@redhat.com) +- rhsmcertd: Remove forgotten old comment (mhorky@redhat.com) +- Stop logging full lscpu output (mhorky@redhat.com) +- Prevent double-logging of syspurpose cache log statement (mhorky@redhat.com) +- Update the log message containing response time statistics + (mhorky@redhat.com) +- CCT-266: Update TLS flags (mhorky@redhat.com) + * Thu Jan 18 2024 Pino Toscano 1.29.40-1 - Translated using Weblate (Korean) (simmon@nplob.com) - Translated using Weblate (Chinese (Simplified) (zh_CN)) diff --git a/systemtest/copr-setup.sh b/systemtest/copr-setup.sh new file mode 100644 index 0000000000..b62fd60500 --- /dev/null +++ b/systemtest/copr-setup.sh @@ -0,0 +1,16 @@ +#!/usr/bin/bash -eux +dnf install -y dnf-plugins-core + +# Determine the repo needed from copr +source /etc/os-release + +if [ "$ID" == "centos" ]; then + ID='centos-stream' +fi +VERSION_MAJOR=$(echo "${VERSION_ID}" | cut -d '.' -f 1) +COPR_REPO="${ID}-${VERSION_MAJOR}-$(uname -m)" + +# Install subscription-manager from COPR repository +dnf remove -y --noautoremove subscription-manager +dnf copr -y enable packit/candlepin-subscription-manager-"${ghprbPullId}" "${COPR_REPO}" +dnf install -y subscription-manager --disablerepo=* --enablerepo=*subscription-manager* diff --git a/systemtest/plans/main.fmf b/systemtest/plans/main.fmf new file mode 100644 index 0000000000..af4bed151e --- /dev/null +++ b/systemtest/plans/main.fmf @@ -0,0 +1,5 @@ +summary: rhsm test suite +discover: + how: fmf +execute: + how: tmt diff --git a/systemtest/tests/integration/main.fmf b/systemtest/tests/integration/main.fmf new file mode 100644 index 0000000000..9d6c3e5b57 --- /dev/null +++ b/systemtest/tests/integration/main.fmf @@ -0,0 +1,3 @@ +summary: Runs tmt tests +test: ./test.sh +duration: 1h diff --git a/systemtest/tests/integration/test.sh b/systemtest/tests/integration/test.sh new file mode 100755 index 0000000000..7349616c45 --- /dev/null +++ b/systemtest/tests/integration/test.sh @@ -0,0 +1,62 @@ +#!/bin/bash +set -ux + +# get to project root +cd ../../../ + +# Check for GitHub pull request ID and install build if needed. +# This is for the downstream PR jobs. +[ -z "${ghprbPullId+x}" ] || ./systemtest/copr-setup.sh + +dnf --setopt install_weak_deps=False install -y \ + podman git-core python3-pip python3-pytest logrotate \ + cairo-gobject-devel gobject-introspection-devel \ + python3-gobject python3-devel + +yum -y groupinstall 'Development Tools' + +python3 -m venv venv +# shellcheck disable=SC1091 +. venv/bin/activate + +# Install requirements for integration tests +pip install -r integration-tests/requirements.txt + +# configuration for the tests +cat < settings.toml +[testing] +candlepin.host = "localhost" +candlepin.port = 8443 +candlepin.insecure = true +candlepin.prefix = "/candlepin" +candlepin.username = "duey" +candlepin.password = "password" +candlepin.org = "donaldduck" +candlepin.activation_keys = ["act-key-01","act-key-02"] +candlepin.environment.names = ["env-name-01","env-name-02"] +candlepin.environment.ids = ["env-id-01","env-id-02"] +EOF + + +# run local candlepin for testing purpose +./integration-tests/scripts/run-local-candlepin.sh + +# create testing data in local candlepin +./integration-tests/scripts/post-activation-keys.sh +./integration-tests/scripts/post-environments.sh + +# There is a problem with SELinux in current version of selinux-roles (for rhsm.service) +# it is a temporary fix +setenforce 0 + +# Run all integration tests. They will use 'testing' environment in configuration +ENV_FOR_DYNACONF=testing pytest --junit-xml=./junit.xml -v integration-tests +retval=$? + +# Copy artifacts of integration tests +if [ -d "$TMT_PLAN_DATA" ]; then + cp ./junit.xml "$TMT_PLAN_DATA/junit.xml" +fi + +# Return exit code of integration tests +exit $retval diff --git a/test/cli_command/test_addons.py b/test/cli_command/test_addons.py index 3239e79957..e69de29bb2 100644 --- a/test/cli_command/test_addons.py +++ b/test/cli_command/test_addons.py @@ -1,81 +0,0 @@ -import json -import sys -from contextlib import ExitStack - -from ..test_managercli import TestCliCommand -from subscription_manager import syspurposelib -from subscription_manager.cli_command.addons import AddonsCommand - -from ..fixture import Capture - -from unittest.mock import patch - - -class TestAddonsCommand(TestCliCommand): - command_class = AddonsCommand - - def _set_syspurpose(self, syspurpose): - """ - Set the mocked out syspurpose to the given dictionary of values. - Assumes it is called after syspurposelib.USER_SYSPURPOSE is mocked out. - :param syspurpose: A dict of values to be set as the syspurpose - :return: None - """ - with open(syspurposelib.USER_SYSPURPOSE, "w") as sp_file: - json.dump(syspurpose, sp_file, ensure_ascii=True) - - def setUp(self): - syspurpose_patch = patch("syspurpose.files.SyncedStore") - sp_patch = syspurpose_patch.start() - self.addCleanup(sp_patch.stop) - super(TestAddonsCommand, self).setUp() - argv_patcher = patch.object(sys, "argv", ["subscription-manager", "syspurpose", "addons"]) - argv_patcher.start() - self.addCleanup(argv_patcher.stop) - syspurposelib.USER_SYSPURPOSE = self.write_tempfile("{}").name - - def tearDown(self): - super(TestAddonsCommand, self).tearDown() - syspurposelib.USER_SYSPURPOSE = "/etc/rhsm/syspurpose/syspurpose.json" - - def test_view(self): - self._test_no_exception([]) - - def test_add(self): - self._test_no_exception(["--add", "test"]) - - def test_add_and_remove(self): - self._test_exception(["--add", "test", "--remove", "something_else"]) - - def test_remove(self): - self._test_no_exception(["--remove", "test"]) - - def test_unset(self): - self._test_no_exception(["--unset"]) - - def test_unset_and_add_and_remove(self): - self._test_exception(["--add", "test", "--remove", "item", "--unset"]) - - def test_add_valid_value(self): - with patch.object(self.cc, "_get_valid_fields") as mock_get_valid_fields: - mock_get_valid_fields.return_value = {"addons": ["ADDON1", "ADDON3", "ADDON2"]} - self.assertTrue(self.cc._is_provided_value_valid("ADDON1")) - with Capture() as cap: - self.assertEqual(self.cc._are_provided_values_valid(["ADDON1"]), []) - self.assertNotIn("Warning: Provided value", cap.out) - - def test_add_invalid_value(self): - with patch.object(self.cc, "_get_valid_fields") as mock_get_valid_fields: - mock_get_valid_fields.return_value = {"addons": ["ADDON1", "ADDON3", "ADDON2"]} - self.assertFalse(self.cc._is_provided_value_valid("test")) - with Capture() as cap: - self.assertEqual(self.cc._are_provided_values_valid(["test"]), ["test"]) - self.assertIn("Warning: Provided value", cap.out) - - def test_no_valid_values(self): - with ExitStack() as stack: - mock_get_valid_fields = stack.enter_context(patch.object(self.cc, "_get_valid_fields")) - cap = stack.enter_context(Capture()) - mock_get_valid_fields.return_value = {"addons": []} - self.assertFalse(self.cc._is_provided_value_valid("test")) - self.assertIn("Warning: This organization does not have", cap.out) diff --git a/test/cli_command/test_attach.py b/test/cli_command/test_attach.py deleted file mode 100644 index 693e928683..0000000000 --- a/test/cli_command/test_attach.py +++ /dev/null @@ -1,204 +0,0 @@ -import contextlib -import os -import sys -import tempfile - -from ..test_managercli import TestCliProxyCommand -from subscription_manager import managercli - -from unittest.mock import patch - - -# Test Attach and Subscribe are the same -class TestAttachCommand(TestCliProxyCommand): - command_class = managercli.AttachCommand - tempdir = None - tempfiles = [] - - @classmethod - def setUpClass(cls): - # Create temp file(s) for processing pool IDs - cls.tempdir = tempfile.TemporaryDirectory() - cls.tempfiles = [ - tempfile.mkstemp(dir=cls.tempdir.name), - tempfile.mkstemp(dir=cls.tempdir.name), - tempfile.mkstemp(dir=cls.tempdir.name), - ] - - os.write(cls.tempfiles[0][0], b"pool1 pool2 pool3 \npool4\npool5\r\npool6\t\tpool7\n pool8\n\n\n") - os.close(cls.tempfiles[0][0]) - os.write(cls.tempfiles[1][0], b"pool1 pool2 pool3 \npool4\npool5\r\npool6\t\tpool7\n pool8\n\n\n") - os.close(cls.tempfiles[1][0]) - # The third temp file intentionally left empty for testing empty sets of data - os.close(cls.tempfiles[2][0]) - - @classmethod - def tearDownClass(cls): - cls.tempdir = None - cls.tempfiles = [] - - def setUp(self): - super(TestAttachCommand, self).setUp() - argv_patcher = patch.object(sys, "argv", ["subscription-manager", "attach"]) - argv_patcher.start() - self.addCleanup(argv_patcher.stop) - - def _test_quantity_exception(self, arg): - try: - self.cc.main(["--pool", "test-pool-id", "--quantity", arg]) - self.cc._validate_options() - except SystemExit as e: - self.assertEqual(e.code, os.EX_USAGE) - else: - self.fail("No Exception Raised") - - def _test_auto_and_quantity_exception(self): - try: - self.cc.main(["--auto", "--quantity", "6"]) - self.cc._validate_options() - except SystemExit as e: - self.assertEqual(e.code, os.EX_USAGE) - else: - self.fail("No Exception Raised") - - def _test_auto_default_and_quantity_exception(self): - try: - self.cc.main(["--quantity", "3"]) - self.cc._validate_options() - except SystemExit as e: - self.assertEqual(e.code, os.EX_USAGE) - else: - self.fail("No Exception Raised") - - def test_zero_quantity(self): - self._test_quantity_exception("0") - - def test_negative_quantity(self): - self._test_quantity_exception("-1") - - def test_text_quantity(self): - try: - self.cc.main(["--quantity", "JarJarBinks"]) - self.cc._validate_options() - except SystemExit as e: - self.assertEqual(e.code, 2) - else: - self.fail("No Exception Raised") - - def test_positive_quantity(self): - self.cc.main(["--pool", "test-pool-id", "--quantity", "1"]) - self.cc._validate_options() - - def test_positive_quantity_with_plus(self): - self.cc.main(["--pool", "test-pool-id", "--quantity", "+1"]) - self.cc._validate_options() - - def test_positive_quantity_as_float(self): - try: - self.cc.main(["--quantity", "2.0"]) - self.cc._validate_options() - except SystemExit as e: - self.assertEqual(e.code, 2) - else: - self.fail("No Exception Raised") - - def _test_pool_file_processing(self, f, expected): - self.cc.main(["--file", f]) - self.cc._validate_options() - - self.assertEqual(expected, self.cc.options.pool) - - def test_pool_option_or_auto_option(self): - self.cc.main(["--auto", "--pool", "1234"]) - self.assertRaises(SystemExit, self.cc._validate_options) - - def test_servicelevel_option_but_no_auto_option(self): - with self.mock_stdin(open(self.tempfiles[1][1])): - self.cc.main(["--servicelevel", "Super", "--file", "-"]) - self.assertRaises(SystemExit, self.cc._validate_options) - - def test_servicelevel_option_with_pool_option(self): - self.cc.main(["--servicelevel", "Super", "--pool", "1232342342313"]) - # need a assertRaises that checks a SystemsExit code and message - self.assertRaises(SystemExit, self.cc._validate_options) - - def test_just_pools_option(self): - self.cc.main(["--pool", "1234"]) - self.cc._validate_options() - - def test_just_auto_option(self): - self.cc.main(["--auto"]) - self.cc._validate_options() - - def test_no_options_defaults_to_auto(self): - self.cc.main([]) - self.cc._validate_options() - - @contextlib.contextmanager - def mock_stdin(self, fileobj): - org_stdin = sys.stdin - sys.stdin = fileobj - - try: - yield - finally: - sys.stdin = org_stdin - - def test_pool_stdin_processing(self): - with self.mock_stdin(open(self.tempfiles[1][1])): - self._test_pool_file_processing( - "-", ["pool1", "pool2", "pool3", "pool4", "pool5", "pool6", "pool7", "pool8"] - ) - - def test_pool_stdin_empty(self): - try: - with self.mock_stdin(open(self.tempfiles[2][1])): - self.cc.main(["--file", "-"]) - self.cc._validate_options() - - except SystemExit as e: - self.assertEqual(e.code, os.EX_DATAERR) - else: - self.fail("No Exception Raised") - - def test_pool_file_processing(self): - self._test_pool_file_processing( - self.tempfiles[0][1], ["pool1", "pool2", "pool3", "pool4", "pool5", "pool6", "pool7", "pool8"] - ) - - def test_pool_file_empty(self): - try: - self.cc.main(["--file", self.tempfiles[2][1]]) - self.cc._validate_options() - - except SystemExit as e: - self.assertEqual(e.code, os.EX_DATAERR) - else: - self.fail("No Exception Raised") - - def test_pool_file_invalid(self): - try: - self.cc.main(["--file", "nonexistant_file.nope"]) - self.cc._validate_options() - except SystemExit as e: - self.assertEqual(e.code, os.EX_DATAERR) - else: - self.fail("No Exception Raised") - - @patch("subscription_manager.cli_command.attach.is_simple_content_access") - def test_auto_attach_sca_mode(self, mock_is_simple_content_access): - """ - Test the case, when SCA mode is used. Auto-attach is not possible in this case - """ - mock_is_simple_content_access.return_value = True - self.cc.main(["--auto"]) - self.cc._validate_options() - - @patch("subscription_manager.cli_command.attach.is_simple_content_access") - def test_attach_sca_mode(self, mock_is_simple_content_access): - """ - Test the case, when SCA mode is used. Attaching of pool is not possible in this case - """ - mock_is_simple_content_access.return_value = True - self.cc.main(["--pool", "123456789"]) - self.cc._validate_options() diff --git a/test/cli_command/test_identity.py b/test/cli_command/test_identity.py index 8826584e79..3d75060e76 100644 --- a/test/cli_command/test_identity.py +++ b/test/cli_command/test_identity.py @@ -7,9 +7,3 @@ class TestIdentityCommand(TestCliProxyCommand): def test_regenerate_no_force(self): self.cc.main(["--regenerate"]) - - def test_token_no_force(self): - self._test_exception(["--token", "eyJhbGciOiJSUzI1NiIsInR5cCIg"]) - - def test_token_with_force(self): - self._test_no_exception(["--regenerate", "--token", "eyJhbGciOiJSUzI1NiIsInR5cCIg", "--force"]) diff --git a/test/cli_command/test_list.py b/test/cli_command/test_list.py index 95576ef86d..7f572a0280 100644 --- a/test/cli_command/test_list.py +++ b/test/cli_command/test_list.py @@ -1,4 +1,3 @@ -import os import sys from ..test_managercli import TestCliProxyCommand @@ -6,10 +5,10 @@ from subscription_manager.entcertlib import CONTENT_ACCESS_CERT_TYPE from subscription_manager.injection import provide, CERT_SORTER -from ..stubs import StubProductCertificate, StubEntitlementCertificate, StubProduct, StubCertSorter, StubPool +from ..stubs import StubProductCertificate, StubEntitlementCertificate, StubProduct, StubCertSorter from ..fixture import Capture -from unittest.mock import patch, Mock, MagicMock +from unittest.mock import patch class TestListCommand(TestCliProxyCommand): @@ -30,160 +29,6 @@ def setUp(self): argv_patcher.start() self.addCleanup(argv_patcher.stop) - def _test_afterdate_option(self, argv, method, should_exit=True, expected_exit_code=0): - msg = "" - with patch.object(sys, "argv", argv): - try: - method() - except SystemExit as e: - self.assertEqual( - e.code, - expected_exit_code, - """Cli should have exited with code '{}', got '{}'""".format(expected_exit_code, e.code), - ) - fail = False - except Exception as e: - fail = True - msg = "Expected SystemExit, got '''{}'''".format(e) - else: - fail = should_exit - if fail: - msg = "Expected SystemExit, No Exception was raised" - - if fail: - self.fail(msg) - - def test_afterdate_option_bad_date(self): - argv = ["subscription-manager", "list", "--all", "--available", "--afterdate", "not_a_real_date"] - self._test_afterdate_option(argv, self.cc.main, expected_exit_code=os.EX_DATAERR) - - def test_afterdate_option_no_date(self): - argv = ["subscription-manager", "list", "--all", "--available", "--afterdate"] - # Error code of 2 is expected from optparse in this case. - self._test_afterdate_option(argv, self.cc.main, expected_exit_code=2) - - def test_afterdate_option_missing_options(self): - # Just missing "available" - argv = ["subscription-manager", "list", "--afterdate", self.valid_date, "--all"] - self._test_afterdate_option(argv, self.cc.main, expected_exit_code=os.EX_USAGE) - - # Missing both - argv = ["subscription-manager", "list", "--afterdate", self.valid_date] - self._test_afterdate_option(argv, self.cc.main, expected_exit_code=os.EX_USAGE) - - def test_afterdate_option_with_ondate(self): - argv = ["subscription-manager", "list", "--afterdate", self.valid_date, "--ondate", self.valid_date] - self._test_afterdate_option(argv, self.cc.main, expected_exit_code=os.EX_USAGE) - - @patch("subscription_manager.managerlib.get_available_entitlements") - def test_afterdate_option_valid(self, es): - def create_pool_list(*args, **kwargs): - return [ - { - "productName": "dummy-name", - "productId": "dummy-id", - "providedProducts": [], - "id": "888888888888", - "management_enabled": True, - "attributes": [{"name": "is_virt_only", "value": "false"}], - "pool_type": "Some Type", - "quantity": "4", - "service_type": "", - "roles": "awsome server", - "service_level": "", - "usage": "Testing", - "addons": "ADDON1", - "contractNumber": "5", - "multi-entitlement": "false", - "startDate": "", - "endDate": "", - "suggested": "2", - } - ] - - es.return_value = create_pool_list() - - argv = ["subscription-manager", "list", "--all", "--available", "--afterdate", self.valid_date] - self._test_afterdate_option(argv, self.cc.main, should_exit=False) - - @patch("subscription_manager.managerlib.get_available_entitlements") - def test_none_wrap_available_pool_id(self, mget_ents): - list_command = managercli.ListCommand() - - def create_pool_list(*args, **kwargs): - return [ - { - "productName": "dummy-name", - "productId": "dummy-id", - "providedProducts": [], - "id": "888888888888", - "management_enabled": True, - "attributes": [{"name": "is_virt_only", "value": "false"}], - "pool_type": "Some Type", - "quantity": "4", - "service_type": "", - "roles": "awesome server", - "service_level": "", - "usage": "Production", - "addons": "", - "contractNumber": "5", - "multi-entitlement": "false", - "startDate": "", - "endDate": "", - "suggested": "2", - } - ] - - mget_ents.return_value = create_pool_list() - - with Capture() as cap: - list_command.main(["--available"]) - self.assertTrue("888888888888" in cap.out) - - @patch("subscription_manager.managerlib.get_available_entitlements") - def test_available_syspurpose_attr(self, mget_ents): - list_command = managercli.ListCommand() - - def create_pool_list(*args, **kwargs): - return [ - { - "productName": "dummy-name", - "productId": "dummy-id", - "providedProducts": [], - "id": "888888888888", - "management_enabled": True, - "attributes": [{"name": "is_virt_only", "value": "false"}], - "pool_type": "Some Type", - "quantity": "4", - "service_type": "", - "roles": "Awesome Server, Cool Server", - "service_level": "Premium", - "usage": "Production", - "addons": "ADDON1,ADDON2", - "contractNumber": "5", - "multi-entitlement": "false", - "startDate": "", - "endDate": "", - "suggested": "2", - } - ] - - mget_ents.return_value = create_pool_list() - - with Capture() as cap: - list_command.main(["--available"]) - self.assertTrue("ADDON1\n" in cap.out) - self.assertTrue("Awesome Server\n" in cap.out) - self.assertTrue("Production" in cap.out) - self.assertTrue("Premium" in cap.out) - - def test_print_consumed_no_ents(self): - with Capture() as captured: - self.cc.print_consumed() - - lines = captured.out.split("\n") - self.assertEqual(len(lines) - 1, 1, "Error output consists of more than one line.") - def test_list_installed(self): """ Test output of 'subscription-manager list --installed' @@ -208,44 +53,6 @@ def test_list_installed(self): assert "Product ID:" in captured.out assert "Version:" in captured.out assert "Arch:" in captured.out - assert "Status:" in captured.out - assert "Status Details:" in captured.out - assert "Starts:" in captured.out - assert "Ends:" in captured.out - - @patch("subscription_manager.cli_command.list.is_simple_content_access") - def test_list_installed_sca_mode(self, is_simple_content_access_mock): - """ - Test output of 'subscription-manager list --installed', when SCA mode is used - """ - is_simple_content_access_mock.return_value = True - - installed_product_certs = [ - StubProductCertificate(product=StubProduct(name="test product", product_id="8675309")), - StubProductCertificate(product=StubProduct(name="another test product", product_id="123456")), - ] - - stub_sorter = StubCertSorter() - - for product_cert in installed_product_certs: - product = product_cert.products[0] - stub_sorter.installed_products[product.id] = product_cert - - provide(CERT_SORTER, stub_sorter) - - with Capture() as captured: - list_command = managercli.ListCommand() - list_command.main(["--installed"]) - assert "Product Name:" in captured.out - assert "Product ID:" in captured.out - assert "Version:" in captured.out - assert "Arch:" in captured.out - # Following attributes should not be printed in SCA mode, because it does not make - # any sense to print them in SCA mode - assert "Status:" not in captured.out - assert "Status Details:" not in captured.out - assert "Starts:" not in captured.out - assert "Ends:" not in captured.out def test_list_installed_with_ctfilter(self): installed_product_certs = [ @@ -294,191 +101,3 @@ def test_list_installed_with_ctfilter(self): installed_product_certs[index].name in captured.out, "Unexpected product was found in output for test data %i" % test_num, ) - - def test_list_consumed_with_ctfilter(self): - consumed = [ - StubEntitlementCertificate( - product=StubProduct(name="Test Entitlement 1", product_id="123"), - provided_products=["test product a", "beta product 1", "shared product", "troll* product?"], - ), - StubEntitlementCertificate( - product=StubProduct(name="Test Entitlement 2", product_id="456"), - provided_products=["test product b", "beta product 1", "shared product", "back\\slash"], - ), - ] - - test_data = [ - ("", (False, False)), - ("test entitlement ?", (True, True)), - ("*entitlement 1", (True, False)), - ("*entitlement 2", (False, True)), - ("input string", (False, False)), - ("*product", (True, True)), - ("*product*", (True, True)), - ("shared pro*nopenopenope", (False, False)), - ("*another*", (False, False)), - ("*product\\?", (True, False)), - ("*product ?", (True, True)), - ("*product?*", (True, True)), - ("*\\?*", (True, False)), - ("*\\\\*", (False, True)), - ("*k\\s*", (False, True)), - ("*23", (True, False)), - ("45?", (False, True)), - ] - - for stubby in consumed: - self.ent_dir.certs.append(stubby) - - for test_num, data in enumerate(test_data): - with Capture() as captured: - list_command = managercli.ListCommand() - list_command.main(["--consumed", "--matches", data[0]]) - - for index, expected in enumerate(data[1]): - if expected: - self.assertTrue( - consumed[index].order.name in captured.out, - "Expected product was not found in output for test data %i" % test_num, - ) - else: - self.assertFalse( - consumed[index].order.name in captured.out, - "Unexpected product was found in output for test data %i" % test_num, - ) - - def test_print_consumed_one_ent_one_product(self): - product = StubProduct("product1") - self.ent_dir.certs.append(StubEntitlementCertificate(product)) - self.cc.sorter = Mock() - self.cc.sorter.get_subscription_reasons_map = Mock() - self.cc.sorter.get_subscription_reasons_map.return_value = {} - self.cc.print_consumed() - - def test_print_consumed_one_ent_no_product(self): - self.ent_dir.certs.append(StubEntitlementCertificate(product=None)) - self.cc.sorter = Mock() - self.cc.sorter.get_subscription_reasons_map = Mock() - self.cc.sorter.get_subscription_reasons_map.return_value = {} - self.cc.print_consumed() - - def test_print_consumed_prints_nothing_with_no_service_level_match(self): - self.ent_dir.certs.append(self.cert_with_service_level) - - with Capture() as captured: - self.cc.print_consumed(service_level="NotFound") - - lines = captured.out.split("\n") - self.assertEqual(len(lines) - 1, 1, "Error output consists of more than one line.") - - def test_print_consumed_prints_enitlement_with_service_level_match(self): - self.ent_dir.certs.append(self.cert_with_service_level) - self.cc.sorter = Mock() - self.cc.sorter.get_subscription_reasons_map = Mock() - self.cc.sorter.get_subscription_reasons_map.return_value = {} - self.cc.print_consumed(service_level="Premium") - - def test_print_consumed_ignores_content_access_cert(self): - self.ent_dir.certs.append(self.cert_with_content_access) - with Capture() as captured: - self.cc.print_consumed(service_level="NotFound") - - lines = captured.out.split("\n") - self.assertEqual(len(lines) - 1, 1, "Error output consists of more than one line.") - - def test_list_installed_with_pidonly(self): - installed_product_certs = [ - StubProductCertificate(product=StubProduct(name="test product*", product_id="8675309")), - StubProductCertificate(product=StubProduct(name="another(?) test\\product", product_id="123456")), - ] - - stub_sorter = StubCertSorter() - - for product_cert in installed_product_certs: - product = product_cert.products[0] - stub_sorter.installed_products[product.id] = product_cert - - provide(CERT_SORTER, stub_sorter) - - try: - with Capture() as captured: - list_command = managercli.ListCommand() - list_command.main(["--installed", "--pool-only"]) - - self.fail("Expected error did not occur") - except SystemExit: - for cert in installed_product_certs: - self.assertFalse(cert.products[0].id in captured.out) - - def test_list_consumed_with_pidonly(self): - consumed = [ - StubEntitlementCertificate( - product=StubProduct(name="Test Entitlement 1", product_id="123"), - pool=StubPool("abc"), - provided_products=["test product a", "beta product 1", "shared product", "troll* product?"], - ), - StubEntitlementCertificate( - product=StubProduct(name="Test Entitlement 2", product_id="456"), - pool=StubPool("def"), - provided_products=["test product b", "beta product 1", "shared product", "back\\slash"], - ), - ] - - for stubby in consumed: - self.ent_dir.certs.append(stubby) - - with Capture() as captured: - list_command = managercli.ListCommand() - list_command.main(["--consumed", "--pool-only"]) - - for cert in consumed: - self.assertFalse(cert.order.name in captured.out) - self.assertTrue(cert.pool.id in captured.out) - - def test_list_consumed_syspurpose_attr_version34(self): - """ - When version of entitlement certificate is 3.4, then subscription-manager should print syspurpose - attributes from the certificate. - """ - product = StubProduct("product1") - ent_cert = StubEntitlementCertificate(product) - ent_cert.order.usage = "Development" - ent_cert.order.roles = ["SP Server", "SP Starter"] - ent_cert.order.addons = ["ADDON1", "ADDON2"] - ent_cert.version = MagicMock() - ent_cert.version.major = 3 - ent_cert.version.minor = 4 - self.ent_dir.certs.append(ent_cert) - self.cc.sorter = Mock() - self.cc.sorter.get_subscription_reasons_map = Mock() - self.cc.sorter.get_subscription_reasons_map.return_value = {} - with Capture() as captured: - self.cc.print_consumed() - self.assertTrue("Add-ons:" in captured.out) - self.assertTrue("ADDON1" in captured.out) - self.assertTrue("ADDON2" in captured.out) - self.assertTrue("Usage:" in captured.out) - self.assertTrue("Development" in captured.out) - self.assertTrue("Roles:" in captured.out) - self.assertTrue("SP Server" in captured.out) - self.assertTrue("SP Starter" in captured.out) - - def test_list_consumed_no_syspurpose_attr_version33(self): - """ - When the version of certificate is older then 3.4, then do not print syspurpose attributes, because - there cannot be any. - """ - product = StubProduct("product1") - ent_cert = StubEntitlementCertificate(product) - ent_cert.version = MagicMock() - ent_cert.version.major = 3 - ent_cert.version.minor = 3 - self.ent_dir.certs.append(ent_cert) - self.cc.sorter = Mock() - self.cc.sorter.get_subscription_reasons_map = Mock() - self.cc.sorter.get_subscription_reasons_map.return_value = {} - with Capture() as captured: - self.cc.print_consumed() - self.assertFalse("Add-ons:" in captured.out) - self.assertFalse("Usage:" in captured.out) - self.assertFalse("Roles:" in captured.out) diff --git a/test/cli_command/test_owners.py b/test/cli_command/test_owners.py index 390ca82f17..f5f8bf89ec 100644 --- a/test/cli_command/test_owners.py +++ b/test/cli_command/test_owners.py @@ -11,6 +11,3 @@ def test_main_server_url(self): def test_insecure(self): self.cc.main(["--insecure"]) - - def test_token_(self): - self.cc.main(["--token", "eyJhbGciOiJSUzI1NiIsInR5cCIg"]) diff --git a/test/cli_command/test_redeem.py b/test/cli_command/test_redeem.py deleted file mode 100644 index 00685443a0..0000000000 --- a/test/cli_command/test_redeem.py +++ /dev/null @@ -1,6 +0,0 @@ -from ..test_managercli import TestCliProxyCommand -from subscription_manager import managercli - - -class TestRedeemCommand(TestCliProxyCommand): - command_class = managercli.RedeemCommand diff --git a/test/cli_command/test_refresh.py b/test/cli_command/test_refresh.py index 9866b51580..223d4a6bcc 100644 --- a/test/cli_command/test_refresh.py +++ b/test/cli_command/test_refresh.py @@ -2,8 +2,8 @@ from ..test_managercli import TestCliProxyCommand from unittest.mock import Mock from subscription_manager import managercli -from subscription_manager.cache import ContentAccessCache, ContentAccessModeCache -from subscription_manager.injection import provide, CONTENT_ACCESS_CACHE, CONTENT_ACCESS_MODE_CACHE +from subscription_manager.cache import ContentAccessCache +from subscription_manager.injection import provide, CONTENT_ACCESS_CACHE class TestRefreshCommand(TestCliProxyCommand): @@ -28,15 +28,8 @@ def test_cache_removed(self): mock_content_access_cache = Mock(spec=ContentAccessCache) mock_content_access_cache.return_value.exists.return_value = True provide(CONTENT_ACCESS_CACHE, mock_content_access_cache) - mock_content_access_mode_cache = Mock(spec=ContentAccessModeCache) - mock_content_access_mode_cache.return_value.exists.return_value = True - provide(CONTENT_ACCESS_MODE_CACHE, mock_content_access_mode_cache) self.cc.main([]) # This cache should not be deleted to be able to use HTTP header 'If-Modified-Since' mock_content_access_cache.return_value.remove.assert_not_called() - # Cache about content access mode should be deleted, because content access mode - # can be changed from SCA to entitlement and vice versa - mock_content_access_mode_cache.return_value.exists.assert_called_once() - mock_content_access_mode_cache.return_value.delete_cache.assert_called_once() diff --git a/test/cli_command/test_register.py b/test/cli_command/test_register.py index 058aee8bff..b0d811af5c 100644 --- a/test/cli_command/test_register.py +++ b/test/cli_command/test_register.py @@ -65,6 +65,3 @@ def test_insecure(self): with patch.object(self.mock_cfg_parser, "save") as mock_save: self._test_no_exception(["--insecure"]) mock_save.assert_called_with() - - def test_token(self): - self._test_no_exception(["--token", "eyJhbGciOiJSUzI1NiIsInR5cCIg"]) diff --git a/test/cli_command/test_remove.py b/test/cli_command/test_remove.py deleted file mode 100644 index d39a5cd21b..0000000000 --- a/test/cli_command/test_remove.py +++ /dev/null @@ -1,37 +0,0 @@ -import os - -from ..test_managercli import TestCliProxyCommand -from subscription_manager import managercli - - -class TestRemoveCommand(TestCliProxyCommand): - command_class = managercli.RemoveCommand - - def test_validate_serial(self): - self.cc.main(["--serial", "12345"]) - self.cc._validate_options() - - def test_validate_serial_not_numbers(self): - self.cc.main(["--serial", "this is not a number"]) - try: - self.cc._validate_options() - except SystemExit as e: - self.assertEqual(e.code, os.EX_USAGE) - - def test_serial_no_value(self): - try: - self.cc.main(["--serial"]) - except SystemExit as e: - self.assertEqual(e.code, 2) - - def test_validate_access_to_remove_by_pool(self): - self.cc.main(["--pool", "a2ee88488bbd32ed8edfa2"]) - self.cc.cp._capabilities = ["remove_by_pool_id"] - self.cc._validate_options() - - def test_validate_no_access_to_remove_by_pool(self): - self.cc.main(["--pool", "a2ee88488bbd32ed8edfa2"]) - try: - self.cc._validate_options() - except SystemExit as e: - self.assertEqual(e.code, 69) diff --git a/test/cli_command/test_repos.py b/test/cli_command/test_repos.py index 7d09dd0949..607952bad2 100644 --- a/test/cli_command/test_repos.py +++ b/test/cli_command/test_repos.py @@ -317,7 +317,7 @@ def test_set_repo_status_enable_all_disable_all(self, mock_repolib): self.cc.cp.setContentOverrides.assert_called_once_with("fake_id", match_dict_list) self.assertTrue(repolib_instance.update.called) - @patch("subscription_manager.repofile.RepoFileBase.path_exists") + @patch("rhsm.repofile.RepoFileBase.path_exists") @patch("subscription_manager.cli_command.repos.YumRepoFile") def test_set_repo_status_when_disconnected(self, mock_repofile, mock_path_exists): mock_path_exists.return_value = True diff --git a/test/cli_command/test_role.py b/test/cli_command/test_role.py index edeee97986..b68a628263 100644 --- a/test/cli_command/test_role.py +++ b/test/cli_command/test_role.py @@ -63,7 +63,6 @@ def test_list_only_username(self): self.cc.options.to_add = False self.cc.options.to_remove = False self.cc.options.list = True - self.cc.options.token = None self.cc.options.username = "admin" self.cc.options.password = None try: @@ -71,22 +70,6 @@ def test_list_only_username(self): except SystemExit as e: self.assertEqual(e.code, os.EX_USAGE) - def test_list_username_and_token(self): - self.cc.options = Mock() - self.cc.is_registered = Mock(return_value=False) - self.cc.options.set = False - self.cc.options.unset = False - self.cc.options.to_add = False - self.cc.options.to_remove = False - self.cc.options.list = True - self.cc.options.token = "TOKEN" - self.cc.options.username = "admin" - self.cc.options.password = "secret" - try: - self.cc._validate_options() - except SystemExit as e: - self.assertEqual(e.code, os.EX_USAGE) - def test_wrong_options_syspurpose_role(self): """It is possible to use --set or --unset options. It's not possible to use both of them together.""" self.cc.options = Mock() @@ -131,22 +114,6 @@ def test_password_on_registered_system(self): except SystemExit as e: self.assertEqual(e.code, os.EX_USAGE) - def test_token_on_registered_system(self): - """Argument --token cannot be used on registered system.""" - self.cc.is_registered = Mock(return_value=True) - self.cc.options = Mock() - self.cc.options.set = None - self.cc.options.unset = None - self.cc.options.to_add = None - self.cc.options.to_remove = None - self.cc.options.show = None - self.cc.options.list = True - self.cc.options.token = "TOKEN" - try: - self.cc._validate_options() - except SystemExit as e: - self.assertEqual(e.code, os.EX_USAGE) - def test_org_on_registered_system(self): """Argument --org cannot be used on registered system.""" self.cc.is_registered = Mock(return_value=True) diff --git a/test/cli_command/test_service_level.py b/test/cli_command/test_service_level.py index 01df66e1f4..17b2448cda 100644 --- a/test/cli_command/test_service_level.py +++ b/test/cli_command/test_service_level.py @@ -145,22 +145,6 @@ def test_password_on_registered_system(self): except SystemExit as e: self.assertEqual(e.code, os.EX_USAGE) - def test_token_on_registered_system(self): - """Argument --token cannot be used on registered system.""" - self.cc.is_registered = Mock(return_value=True) - self.cc.options = Mock() - self.cc.options.set = None - self.cc.options.unset = None - self.cc.options.to_add = None - self.cc.options.to_remove = None - self.cc.options.show = None - self.cc.options.list = True - self.cc.options.token = "TOKEN" - try: - self.cc._validate_options() - except SystemExit as e: - self.assertEqual(e.code, os.EX_USAGE) - def test_org_on_registered_system(self): """Argument --org cannot be used on registered system.""" self.cc.is_registered = Mock(return_value=True) diff --git a/test/data/anaconda-ks.cfg b/test/data/anaconda-ks.cfg index b98f7a434c..1f843b4547 100644 --- a/test/data/anaconda-ks.cfg +++ b/test/data/anaconda-ks.cfg @@ -59,9 +59,6 @@ clearpart --all --initlabel --drives=sda # Yeah, strip()'ing passwords seems like a bad idea. password = password - auto-attach = True - # If we should attempt to auto-attach - servicelevel = Premium %end diff --git a/test/fixture.py b/test/fixture.py index ed927de81d..e15bfd044a 100644 --- a/test/fixture.py +++ b/test/fixture.py @@ -203,7 +203,6 @@ def unstub_conf(): inj.provide(inj.SUPPORTED_RESOURCES_CACHE, stubs.StubSupportedResourcesCache()) inj.provide(inj.SYSPURPOSE_VALID_FIELDS_CACHE, stubs.StubSyspurposeValidFieldsCache()) inj.provide(inj.CURRENT_OWNER_CACHE, stubs.StubCurrentOwnerCache) - inj.provide(inj.CONTENT_ACCESS_MODE_CACHE, stubs.StubContentAccessModeCache()) inj.provide(inj.OVERRIDE_STATUS_CACHE, stubs.StubOverrideStatusCache()) inj.provide(inj.RELEASE_STATUS_CACHE, stubs.StubReleaseStatusCache()) inj.provide(inj.AVAILABLE_ENTITLEMENT_CACHE, stubs.StubAvailableEntitlementsCache()) diff --git a/test/functional_tests/README.md b/test/functional_tests/README.md index 02c5e2cf7b..231e8b1562 100644 --- a/test/functional_tests/README.md +++ b/test/functional_tests/README.md @@ -24,7 +24,6 @@ How to run functional tests? cd ./ansible_playbooks ansible-playbook ./configure_package_manager.yml --extra-vars="candlepin_hostname=" ansible-playbook ./register_system.yml - ansible-playbook ./attach_subscriptions.yml ansible-playbook ./test_install_remove_packages.yml ansible-playbook ./test_not_remove_prod_cert_for_disabled_repo.yml ``` diff --git a/test/functional_tests/ansible_playbooks/attach_subscriptions.yml b/test/functional_tests/ansible_playbooks/attach_subscriptions.yml deleted file mode 100644 index e0bb6152d9..0000000000 --- a/test/functional_tests/ansible_playbooks/attach_subscriptions.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -- hosts: clients - vars: - subscription_skus: ['awesomeos-x86_64', 'awesomeos-all-x86-cont'] - remote_user: root - tasks: - - # Try to find pool ids - - name: try to find pool ids - shell: subscription-manager list --available --matches={{ item }} | gawk '/Pool ID:/{ print $3; exit }' - register: cmd_output - with_items: "{{ subscription_skus }}" - - - set_fact: - pool_ids: "{{ cmd_output | json_query('results[*].stdout_lines[0]') }}" - - - debug: - msg: "{{ pool_ids }}" - - - name: attach pools - shell: subscription-manager attach --pool {{ item }} - with_items: "{{ pool_ids }}" - diff --git a/test/rhsm/functional/test_connection.py b/test/rhsm/functional/test_connection.py index 7660ed5ca5..4f7d38f445 100644 --- a/test/rhsm/functional/test_connection.py +++ b/test/rhsm/functional/test_connection.py @@ -22,7 +22,6 @@ from rhsm.connection import ( ContentConnection, UEPConnection, - BaseRestLib, UnauthorizedException, ForbiddenException, RestlibException, @@ -121,40 +120,6 @@ def tearDown(self): self.cp.unregisterConsumer(self.consumer_uuid) -@subman_marker_functional -class BindRequestTests(unittest.TestCase): - def setUp(self): - self.cp = UEPConnection(username="admin", password="admin", insecure=True) - - consumerInfo = self.cp.registerConsumer("test-consumer", "system", owner="admin") - self.consumer_uuid = consumerInfo["uuid"] - - @patch.object(BaseRestLib, "validateResult") - @patch("rhsm.connection.drift_check", return_value=False) - @patch("httplib.HTTPSConnection", autospec=True) - def test_bind_no_args(self, mock_conn, mock_drift, mock_validate): - self.cp.bind(self.consumer_uuid) - - # verify we called request() with kwargs that include 'body' as None - # Specifically, we are checking that passing in "" to post_request, as - # it does by default, results in None here. bin() passes no args there - # so we use the default, "". See bz #907536 - for name, args, kwargs in mock_conn.mock_calls: - if name == "().request": - self.assertEqual(None, kwargs["body"]) - - @patch.object(BaseRestLib, "validateResult") - @patch("rhsm.connection.drift_check", return_value=False) - @patch("httplib.HTTPSConnection", autospec=True) - def test_bind_by_pool(self, mock_conn, mock_drift, mock_validate): - # this test is just to verify we make the httplib connection with - # right args, we don't validate the bind here - self.cp.bindByEntitlementPool(self.consumer_uuid, "123121111", "1") - for name, args, kwargs in mock_conn.mock_calls: - if name == "().request": - self.assertEqual(None, kwargs["body"]) - - @subman_marker_functional class ContentConnectionTests(unittest.TestCase): def testInsecure(self): diff --git a/test/rhsm/unit/test_connection.py b/test/rhsm/unit/test_connection.py index 90c750bb26..be562b5fd8 100644 --- a/test/rhsm/unit/test_connection.py +++ b/test/rhsm/unit/test_connection.py @@ -43,7 +43,6 @@ import subscription_manager.injection as inj from unittest.mock import Mock, patch, mock_open -from datetime import date from rhsm import ourjson as json from collections import namedtuple @@ -207,24 +206,6 @@ def test_has_proper_language_header_not_utf8(self, mock_locale): self.cp.conn._set_accept_language_in_header() self.assertEqual(self.cp.conn.headers["Accept-Language"], "ja-jp") - def test_entitle_date(self): - self.cp.conn = Mock() - self.cp.conn.request_post = Mock(return_value=[]) - self.cp.bind("abcd", date(2011, 9, 2)) - self.cp.conn.request_post.assert_called_with( - "/consumers/abcd/entitlements?entitle_date=2011-09-02", - description="Updating subscriptions", - ) - - def test_no_entitle_date(self): - self.cp.conn = Mock() - self.cp.conn.request_post = Mock(return_value=[]) - self.cp.bind("abcd") - self.cp.conn.request_post.assert_called_with( - "/consumers/abcd/entitlements", - description="Updating subscriptions", - ) - def test_clean_up_prefix(self): self.assertTrue(self.cp.handler == "/Test/") @@ -871,6 +852,18 @@ def test_429_body(self): else: self.fail("Should have raised a RateLimitExceededException") + def test_429_weird_case(self): + content = '{"errors": ["TooFast"]}' + headers = {"RETry-aFteR": 20} + try: + self.vr("429", content, headers) + except RateLimitExceededException as e: + self.assertEqual(20, e.retry_after) + self.assertEqual("TooFast, retry access after: 20 seconds.", e.msg) + self.assertEqual("429", e.code) + else: + self.fail("Should have raised a RateLimitExceededException") + def test_500_empty(self): try: self.vr("500", "") diff --git a/test/rhsmlib/dbus/test_attach.py b/test/rhsmlib/dbus/test_attach.py deleted file mode 100644 index c87c652184..0000000000 --- a/test/rhsmlib/dbus/test_attach.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# This software is licensed to you under the GNU General Public License, -# version 2 (GPLv2). There is NO WARRANTY for this software, express or -# implied, including the implied warranties of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -# along with this software; if not, see -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -# -# Red Hat trademarks are not licensed under GPLv2. No permission is -# granted to use or replicate Red Hat trademarks that are incorporated -# in this software or its documentation. - -import dbus -from unittest import mock - -from rhsmlib.dbus.objects.attach import AttachDBusImplementation -from subscription_manager.i18n import Locale - -from test.rhsmlib.base import SubManDBusFixture -from test.rhsmlib.services.test_attach import CONTENT_JSON - - -class TestAttachDBusObject(SubManDBusFixture): - def setUp(self) -> None: - super().setUp() - self.impl = AttachDBusImplementation() - - is_simple_content_access_patch = mock.patch( - "rhsmlib.dbus.objects.attach.is_simple_content_access", - name="is_simple_content_access", - ) - self.patches["is_simple_content_access"] = is_simple_content_access_patch.start() - self.addCleanup(is_simple_content_access_patch.stop) - self.patches["is_simple_content_access"].return_value = False - - is_registered_patch = mock.patch( - "rhsmlib.dbus.objects.attach.AttachDBusImplementation.is_registered", - name="is_registered", - ) - self.patches["is_registered"] = is_registered_patch.start() - self.addCleanup(is_registered_patch.stop) - self.patches["is_registered"].return_value = True - - update_patch = mock.patch( - "subscription_manager.certlib.BaseActionInvoker.update", - name="update", - ) - self.patches["update"] = update_patch.start() - self.addCleanup(update_patch.stop) - self.patches["update"].return_value = None - - AttachService_patch = mock.patch( - "rhsmlib.dbus.objects.attach.AttachService", - name="AttachService", - autospec=True, - ) - self.mock_attach = AttachService_patch.start().return_value - self.addCleanup(AttachService_patch.stop) - - def tearDown(self): - Locale.set(self.LOCALE) - - def test_PoolAttach(self): - self.mock_attach.attach_pool.return_value = CONTENT_JSON - - expected = [CONTENT_JSON, CONTENT_JSON] - result = self.impl.pool_attach(["x", "y"], 1, {}) - self.assertEqual(result, expected) - - def test_PoolAttach__proxy(self): - self.mock_attach.attach_pool.return_value = CONTENT_JSON - - expected = [CONTENT_JSON, CONTENT_JSON] - result = self.impl.pool_attach( - ["x", "y"], - 1, - { - "proxy_hostname": "proxy.company.com", - "proxy_port": "3128", - "proxy_user": "user", - "proxy_password": "password", - }, - ) - self.assertEqual(result, expected) - - def test_PoolAttach__de(self): - self.mock_attach.attach_pool.return_value = CONTENT_JSON - Locale.set("de") - - expected = [CONTENT_JSON, CONTENT_JSON] - result = self.impl.pool_attach(["x", "y"], 1, {}) - self.assertEqual(expected, result) - - def test_PoolAttach__de_DE(self): - self.mock_attach.attach_pool.return_value = CONTENT_JSON - Locale.set("de_DE") - - expected = [CONTENT_JSON, CONTENT_JSON] - result = self.impl.pool_attach(["x", "y"], 1, {}) - self.assertEqual(expected, result) - - def test_PoolAttach__de_DE_utf8(self): - self.mock_attach.attach_pool.return_value = CONTENT_JSON - Locale.set("de_DE.utf-8") - - expected = [CONTENT_JSON, CONTENT_JSON] - result = self.impl.pool_attach(["x", "y"], 1, {}) - self.assertEqual(expected, result) - - def test_PoolAttach__de_DE_UTF8(self): - self.mock_attach.attach_pool.return_value = CONTENT_JSON - Locale.set("de_DE.UTF-8") - - expected = [CONTENT_JSON, CONTENT_JSON] - result = self.impl.pool_attach(["x", "y"], 1, {}) - self.assertEqual(expected, result) - - def test_PoolAttach__sca(self): - self.patches["is_simple_content_access"].return_value = True - self.mock_attach.attach_pool.return_value = CONTENT_JSON - Locale.set("de_DE.UTF-8") - - # TODO: Change to assertRaises when auto-attach is not supported in SCA mode - # BZ 2049101, BZ 2049620 - - expected = [CONTENT_JSON, CONTENT_JSON] - result = self.impl.pool_attach(["x", "y"], 1, {}) - self.assertEqual(expected, result) - - def test_PoolAttach__not_registered(self): - self.mock_attach.attach_pool.return_value = CONTENT_JSON - self.patches["is_registered"].return_value = False - - with self.assertRaisesRegex(dbus.DBusException, "requires the consumer to be registered"): - self.impl.pool_attach(["x", "y"], 1, {}) - - def test_AutoAttach(self): - self.mock_attach.attach_auto.return_value = CONTENT_JSON - - result = self.impl.auto_attach("service_level", {}) - self.assertEqual(CONTENT_JSON, result) - - def test_AutoAttach__sca(self): - self.patches["is_simple_content_access"].return_value = True - self.mock_attach.attach_auto.return_value = CONTENT_JSON - - # TODO: Change to assertRaises when auto-attach is not supported in SCA mode - # BZ 2049101, BZ 2049620 - - result = self.impl.auto_attach("service_level", {}) - self.assertEqual(CONTENT_JSON, result) - - def test_AutoAttach__not_registered(self): - self.mock_attach.attach_pool.return_value = CONTENT_JSON - self.patches["is_registered"].return_value = False - - with self.assertRaisesRegex(dbus.DBusException, "requires the consumer to be registered"): - self.impl.auto_attach("service level", {}) diff --git a/test/rhsmlib/dbus/test_entitlement.py b/test/rhsmlib/dbus/test_entitlement.py index 21fb477c5d..fc0ddad0cb 100644 --- a/test/rhsmlib/dbus/test_entitlement.py +++ b/test/rhsmlib/dbus/test_entitlement.py @@ -34,77 +34,3 @@ def test_get_status(self): result = self.impl.get_status("") self.assertEqual(expected, result) - - def test_remove_entitlements_by_serials(self): - remove_entitlements_by_serials_patch = mock.patch( - "rhsmlib.services.entitlement.EntitlementService.remove_entitlements_by_serials", - name="remove_entitlements_by_serials", - ) - self.patches["remove_entitlements_by_serials"] = remove_entitlements_by_serials_patch.start() - self.addCleanup(remove_entitlements_by_serials_patch.stop) - - removed_nonremoved = (["123"], []) - self.patches["remove_entitlements_by_serials"].return_value = removed_nonremoved - - expected = removed_nonremoved[0] - result = self.impl.remove_entitlements_by_serials(["123"], {}) - self.assertEqual(expected, result) - - def test_remove_entitlements_by_serials__multiple(self): - remove_entitlements_by_serials_patch = mock.patch( - "rhsmlib.services.entitlement.EntitlementService.remove_entitlements_by_serials", - name="remove_entitlements_by_serials", - ) - self.patches["remove_entitlements_by_serials"] = remove_entitlements_by_serials_patch.start() - self.addCleanup(remove_entitlements_by_serials_patch.stop) - - removed_nonremoved = (["123", "456"], []) - self.patches["remove_entitlements_by_serials"].return_value = removed_nonremoved - - expected = removed_nonremoved[0] - result = self.impl.remove_entitlements_by_serials(["123", "456"], {}) - self.assertEqual(expected, result) - - def test_remove_entitlements_by_serials__good_and_bad(self): - remove_entitlements_by_serials_patch = mock.patch( - "rhsmlib.services.entitlement.EntitlementService.remove_entitlements_by_serials", - name="remove_entitlements_by_serials", - ) - self.patches["remove_entitlements_by_serials"] = remove_entitlements_by_serials_patch.start() - self.addCleanup(remove_entitlements_by_serials_patch.stop) - - removed_nonremoved = (["123"], ["456"]) - self.patches["remove_entitlements_by_serials"].return_value = removed_nonremoved - - expected = removed_nonremoved[0] - result = self.impl.remove_entitlements_by_serials(["123", "789"], {}) - self.assertEqual(expected, result) - - def test_remove_entitlements_by_pool_ids(self): - remove_entitlements_by_pool_ids_patch = mock.patch( - "rhsmlib.services.entitlement.EntitlementService.remove_entitlements_by_pool_ids", - name="remove_entitlements_by_pool_ids", - ) - self.patches["remove_entitlements_by_pool_ids"] = remove_entitlements_by_pool_ids_patch.start() - self.addCleanup(remove_entitlements_by_pool_ids_patch.stop) - - removed_nonremoved_serials = (["123"], [], ["456"]) - self.patches["remove_entitlements_by_pool_ids"].return_value = removed_nonremoved_serials - - expected = removed_nonremoved_serials[2] - result = self.impl.remove_entitlements_by_pool_ids(["123"], {}) - self.assertEqual(expected, result) - - def test_remove_all_entitlements(self): - remove_all_entitlements_patch = mock.patch( - "rhsmlib.services.entitlement.EntitlementService.remove_all_entitlements", - name="remove_all_entitlements", - ) - self.patches["remove_all_entitlements"] = remove_all_entitlements_patch.start() - self.addCleanup(remove_all_entitlements_patch.stop) - - records = {"deletedRecords": 1} - self.patches["remove_all_entitlements"].return_value = records - - result = self.impl.remove_all_entitlements({}) - self.assertEqual(records, result) diff --git a/test/rhsmlib/dbus/test_register.py b/test/rhsmlib/dbus/test_register.py index e4934e8849..3715d17c25 100644 --- a/test/rhsmlib/dbus/test_register.py +++ b/test/rhsmlib/dbus/test_register.py @@ -26,45 +26,6 @@ from test.rhsmlib.base import SubManDBusFixture -CONSUMER_CONTENT_JSON = """{"hypervisorId": "foo", - "serviceLevel": "", - "autoheal": true, - "idCert": { - "key": "FAKE_KEY", - "cert": "FAKE_CERT", - "serial" : { - "id" : 5196045143213189102, - "revoked" : false, - "collected" : false, - "expiration" : "2033-04-25T18:03:06+0000", - "serial" : 5196045143213189102, - "created" : "2017-04-25T18:03:06+0000", - "updated" : "2017-04-25T18:03:06+0000" - }, - "id" : "8a8d011e5ba64700015ba647fbd20b88", - "created" : "2017-04-25T18:03:07+0000", - "updated" : "2017-04-25T18:03:07+0000" - }, - "owner": { - "href": "/owners/admin", - "displayName": "Admin Owner", - "id": "ff808081550d997c01550d9adaf40003", - "key": "admin", - "contentAccessMode": "entitlement" - }, - "href": "/consumers/c1b8648c-6f0a-4aa5-b34e-b9e62c0e4364", - "facts": {}, "id": "ff808081550d997c015511b0406d1065", - "uuid": "c1b8648c-6f0a-4aa5-b34e-b9e62c0e4364", - "guestIds": null, "capabilities": null, - "environment": null, "installedProducts": null, - "canActivate": false, "type": {"manifest": false, - "id": "1000", "label": "system"}, "annotations": null, - "username": "admin", "updated": "2016-06-02T15:16:51+0000", - "lastCheckin": null, "entitlementCount": 0, "releaseVer": - {"releaseVer": null}, "entitlementStatus": "valid", "name": - "test.example.com", "created": "2016-06-02T15:16:51+0000", - "contentTags": null, "dev": false}""" - CONSUMER_CONTENT_JSON_SCA = """{"hypervisorId": null, "serviceLevel": "", "autoheal": true, @@ -104,158 +65,6 @@ "test.example.com", "created": "2016-06-02T15:16:51+0000", "contentTags": null, "dev": false}""" -ENABLED_CONTENT = """[ { - "created" : "2022-06-30T13:24:33+0000", - "updated" : "2022-06-30T13:24:33+0000", - "id" : "16def3d98d6549f8a3649f723a76991c", - "consumer" : { - "id" : "4028face81aa047e0181b4c8e1170bdc", - "uuid" : "8503a41a-6ce2-480c-bc38-b67d6aa6dd20", - "name" : "thinkpad-t580", - "href" : "/consumers/8503a41a-6ce2-480c-bc38-b67d6aa6dd20" - }, - "pool" : { - "created" : "2022-06-28T11:14:35+0000", - "updated" : "2022-06-30T13:24:33+0000", - "id" : "4028face81aa047e0181aa052f740360", - "type" : "NORMAL", - "owner" : { - "id" : "4028face81aa047e0181aa0490e30002", - "key" : "admin", - "displayName" : "Admin Owner", - "href" : "/owners/admin", - "contentAccessMode" : "entitlement" - }, - "activeSubscription" : true, - "sourceEntitlement" : null, - "quantity" : 5, - "startDate" : "2022-06-23T13:14:26+0000", - "endDate" : "2023-06-23T13:14:26+0000", - "attributes" : [ ], - "restrictedToUsername" : null, - "contractNumber" : "0", - "accountNumber" : "6547096716", - "orderNumber" : "order-23226139", - "consumed" : 1, - "exported" : 0, - "branding" : [ ], - "calculatedAttributes" : { - "compliance_type" : "Standard" - }, - "upstreamPoolId" : "upstream-05736148", - "upstreamEntitlementId" : null, - "upstreamConsumerId" : null, - "productName" : "SP Server Standard (U: Development, R: SP Server)", - "productId" : "sp-server-dev", - "productAttributes" : [ { - "name" : "management_enabled", - "value" : "1" - }, { - "name" : "usage", - "value" : "Development" - }, { - "name" : "roles", - "value" : "SP Server" - }, { - "name" : "variant", - "value" : "ALL" - }, { - "name" : "sockets", - "value" : "128" - }, { - "name" : "support_level", - "value" : "Standard" - }, { - "name" : "support_type", - "value" : "L1-L3" - }, { - "name" : "arch", - "value" : "ALL" - }, { - "name" : "type", - "value" : "MKT" - }, { - "name" : "version", - "value" : "1.0" - } ], - "stackId" : null, - "stacked" : false, - "sourceStackId" : null, - "developmentPool" : false, - "href" : "/pools/4028face81aa047e0181aa052f740360", - "derivedProductAttributes" : [ ], - "derivedProductId" : null, - "derivedProductName" : null, - "providedProducts" : [ { - "productId" : "99000", - "productName" : "SP Server Bits" - } ], - "derivedProvidedProducts" : [ ], - "subscriptionSubKey" : "master", - "subscriptionId" : "srcsub-45255972", - "locked" : false - }, - "quantity" : 1, - "certificates" : [ { - "created" : "2022-06-30T13:24:33+0000", - "updated" : "2022-06-30T13:24:33+0000", - "id" : "4028face81aa047e0181b4c8e4b90be1", - "key" : "-----BEGIN PRIVATE KEY-----REDACTED-----END PRIVATE KEY-----", - "cert" : "-----BEGIN CERTIFICATE-----REDACTED-----END RSA SIGNATURE-----", - "serial" : { - "created" : "2022-06-30T13:24:33+0000", - "updated" : "2022-06-30T13:24:33+0000", - "id" : 3712610178651551557, - "serial" : 3712610178651551557, - "expiration" : "2023-06-23T13:14:26+0000", - "revoked" : false - } - } ], - "startDate" : "2022-06-23T13:14:26+0000", - "endDate" : "2023-06-23T13:14:26+0000", - "href" : null -} ] -""" - -# Following consumer do not contain information about content access mode -OLD_CONSUMER_CONTENT_JSON = """{"hypervisorId": null, - "serviceLevel": "", - "autoheal": true, - "idCert": { - "key": "FAKE_KEY", - "cert": "FAKE_CERT", - "serial" : { - "id" : 5196045143213189102, - "revoked" : false, - "collected" : false, - "expiration" : "2033-04-25T18:03:06+0000", - "serial" : 5196045143213189102, - "created" : "2017-04-25T18:03:06+0000", - "updated" : "2017-04-25T18:03:06+0000" - }, - "id" : "8a8d011e5ba64700015ba647fbd20b88", - "created" : "2017-04-25T18:03:07+0000", - "updated" : "2017-04-25T18:03:07+0000" - }, - "owner": { - "href": "/owners/admin", - "displayName": "Admin Owner", - "id": "ff808081550d997c01550d9adaf40003", - "key": "admin" - }, - "href": "/consumers/c1b8648c-6f0a-4aa5-b34e-b9e62c0e4364", - "facts": {}, "id": "ff808081550d997c015511b0406d1065", - "uuid": "c1b8648c-6f0a-4aa5-b34e-b9e62c0e4364", - "guestIds": null, "capabilities": null, - "environments": null, "installedProducts": null, - "canActivate": false, "type": {"manifest": false, - "id": "1000", "label": "system"}, "annotations": null, - "username": "admin", "updated": "2016-06-02T15:16:51+0000", - "lastCheckin": null, "entitlementCount": 0, "releaseVer": - {"releaseVer": null}, "entitlementStatus": "valid", "name": - "test.example.com", "created": "2016-06-02T15:16:51+0000", - "contentTags": null, "dev": false}""" - OWNERS_CONTENT_JSON = """[ { "autobindDisabled": false, @@ -314,6 +123,77 @@ ] """ +ENVIRONMENTS_CONTENT_JSON = """[ + { + "created": "2024-11-07T20:01:47+0000", + "updated": "2024-11-07T20:01:47+0000", + "id": "fake-id", + "name": "test-environment", + "description": "test description", + "contentPrefix": null, + "type": "content-template", + "environmentContent": [] + }, + { + "created": "2024-11-07T20:01:47+0000", + "updated": "2024-11-07T20:01:47+0000", + "id": "fake-id-2", + "name": "test-environment-2", + "description": "test description", + "contentPrefix": null, + "type": "content-template", + "environmentContent": [] + }, + { + "created": "2024-11-07T20:01:47+0000", + "updated": "2024-11-07T20:01:47+0000", + "id": "fake-id-3", + "name": "test-environment-3", + "description": "test description", + "contentPrefix": null, + "type": null, + "environmentContent": [] + }, + { + "created": "2024-11-07T20:01:47+0000", + "updated": "2024-11-07T20:01:47+0000", + "id": "fake-id-4", + "name": "test-environment-4", + "description": "test description", + "contentPrefix": null, + "environmentContent": [] + } +] +""" + +ENVIRONMENTS_DBUS_JSON = """[ + { + "id": "fake-id", + "name": "test-environment", + "description": "test description", + "type": "content-template" + }, + { + "id": "fake-id-2", + "name": "test-environment-2", + "description": "test description", + "type": "content-template" + }, + { + "id": "fake-id-3", + "name": "test-environment-3", + "description": "test description", + "type": "" + }, + { + "id": "fake-id-4", + "name": "test-environment-4", + "description": "test description", + "type": "" + } +] +""" + class RegisterDBusObjectTest(SubManDBusFixture): socket_dir: Optional[tempfile.TemporaryDirectory] = None @@ -426,13 +306,6 @@ def setUp(self) -> None: self.patches["update"] = update_patch.start() self.addCleanup(update_patch.stop) - attach_auto_patch = mock.patch( - "rhsmlib.dbus.objects.register.AttachService.attach_auto", - name="attach_auto", - ) - self.patches["attach_auto"] = attach_auto_patch.start() - self.addCleanup(attach_auto_patch.stop) - build_uep_patch = mock.patch( "rhsmlib.dbus.base_object.BaseImplementation.build_uep", name="build_uep", @@ -442,7 +315,7 @@ def setUp(self) -> None: self.patches["update"].return_value = None def test_Register(self): - expected = json.loads(CONSUMER_CONTENT_JSON) + expected = json.loads(CONSUMER_CONTENT_JSON_SCA) self.patches["register"].return_value = expected self.patches["is_registered"].return_value = False @@ -452,7 +325,7 @@ def test_Register(self): self.assertEqual(expected, result) def test_Register__with_force_option(self): - expected = json.loads(CONSUMER_CONTENT_JSON) + expected = json.loads(CONSUMER_CONTENT_JSON_SCA) self.patches["register"].return_value = expected self.patches["unregister"].return_value = None self.patches["is_registered"].return_value = True @@ -463,7 +336,7 @@ def test_Register__with_force_option(self): self.assertEqual(expected, result) def test_Register__already_registered(self): - expected = json.loads(CONSUMER_CONTENT_JSON) + expected = json.loads(CONSUMER_CONTENT_JSON_SCA) self.patches["register"].return_value = expected self.patches["unregister"].return_value = None self.patches["is_registered"].return_value = True @@ -473,9 +346,8 @@ def test_Register__already_registered(self): def test_Register__enable_content(self): """Test including 'enable_content' in entitlement mode with no content.""" - expected = json.loads(CONSUMER_CONTENT_JSON) + expected = json.loads(CONSUMER_CONTENT_JSON_SCA) self.patches["register"].return_value = expected - self.patches["attach_auto"].return_value = [] self.patches["is_registered"].return_value = False result = self.impl.register_with_credentials( @@ -483,18 +355,6 @@ def test_Register__enable_content(self): ) self.assertEqual(expected, result) - def test_Register__enable_content_with_content(self): - """Test including 'enable_content' in entitlement mode with some content.""" - expected = json.loads(ENABLED_CONTENT) - self.patches["register"].return_value = json.loads(CONSUMER_CONTENT_JSON) - self.patches["attach_auto"].return_value = expected - self.patches["is_registered"].return_value = False - - result = self.impl.register_with_credentials( - "org", {"username": "username", "password": "password", "enable_content": "1"}, {} - ) - self.assertEqual(expected, result["enabledContent"]) - def test_Register__enable_content__sca(self): """Test including 'enable_content' in SCA mode.""" expected = json.loads(CONSUMER_CONTENT_JSON_SCA) @@ -519,8 +379,23 @@ def test_GetOrgs(self): result = self.impl.get_organizations({"username": "username", "password": "password"}) self.assertEqual(expected, result) + def test_GetEnvironments(self): + self.patches["is_registered"].return_value = False + mock_cp = mock.Mock(spec=connection.UEPConnection, name="UEPConnection") + mock_cp.username = "username" + mock_cp.password = "password" + mock_cp.getEnvironmentList = mock.Mock() + mock_cp.getEnvironmentList.return_value = json.loads(ENVIRONMENTS_CONTENT_JSON) + self.patches["build_uep"].return_value = mock_cp + + expected = json.loads(ENVIRONMENTS_DBUS_JSON) + result = self.impl.get_environments( + {"username": "username", "password": "password", "org_id": "org_id"} + ) + self.assertEqual(expected, result) + def test_RegisterWithActivationKeys(self): - expected = json.loads(CONSUMER_CONTENT_JSON) + expected = json.loads(CONSUMER_CONTENT_JSON_SCA) self.patches["is_registered"].return_value = False self.patches["register"].return_value = expected @@ -532,7 +407,7 @@ def test_RegisterWithActivationKeys(self): self.assertEqual(expected, result) def test_RegisterWithActivationKeys__already_registered(self): - expected = json.loads(CONSUMER_CONTENT_JSON) + expected = json.loads(CONSUMER_CONTENT_JSON_SCA) self.patches["is_registered"].return_value = True self.patches["register"].return_value = expected @@ -544,7 +419,7 @@ def test_RegisterWithActivationKeys__already_registered(self): ) def test_RegisterWithActivationKeys__with_force_option(self): - expected = json.loads(CONSUMER_CONTENT_JSON) + expected = json.loads(CONSUMER_CONTENT_JSON_SCA) self.patches["is_registered"].return_value = True self.patches["unregister"].return_value = None self.patches["register"].return_value = expected diff --git a/test/rhsmlib/facts/test_cloud_facts.py b/test/rhsmlib/facts/test_cloud_facts.py index 2a8230622f..ea1a34f1c3 100644 --- a/test/rhsmlib/facts/test_cloud_facts.py +++ b/test/rhsmlib/facts/test_cloud_facts.py @@ -54,7 +54,7 @@ }, "customData": "", "location": "westeurope", - "name": "foo-bar", + "name": "foo-vm-name", "offer": "RHEL", "osType": "Linux", "placementGroupId": "", @@ -73,7 +73,7 @@ } ], "publisher": "RedHat", - "resourceGroupName": "foo-bar", + "resourceGroupName": "foo-group-name", "resourceId": "/subscriptions/01234567-0123-0123-0123-012345679abc/resourceGroups/foo-bar/providers/Microsoft.Compute/virtualMachines/foo", "sku": "8.1-ci", "storageProfile": { @@ -172,6 +172,8 @@ AZURE_INSTANCE_ID = "12345678-1234-1234-1234-123456789abc" AZURE_SKU = "8.1-ci" AZURE_OFFER = "RHEL" +AZURE_VM_NAME = "foo-vm-name" +AZURE_RESOURCE_GROUP_NAME = "foo-group-name" AZURE_SUBSCRIPTION_ID = "01234567-0123-0123-0123-012345679abc" # There is no list of valid values of locations, extended locations and # types of extended locations. Value "microsoftlondon" probably does not @@ -324,6 +326,10 @@ def test_get_azure_facts(self): self.assertEqual(facts["azure_sku"], AZURE_SKU) self.assertIn("azure_offer", facts) self.assertEqual(facts["azure_offer"], AZURE_OFFER) + self.assertIn("azure_vm_name", facts) + self.assertEqual(facts["azure_vm_name"], AZURE_VM_NAME) + self.assertIn("azure_resource_group_name", facts) + self.assertEqual(facts["azure_resource_group_name"], AZURE_RESOURCE_GROUP_NAME) self.assertIn("azure_subscription_id", facts) self.assertEqual(facts["azure_subscription_id"], AZURE_SUBSCRIPTION_ID) self.assertIn("azure_location", facts) diff --git a/test/rhsmlib/services/test_attach.py b/test/rhsmlib/services/test_attach.py deleted file mode 100644 index 80d0a16512..0000000000 --- a/test/rhsmlib/services/test_attach.py +++ /dev/null @@ -1,153 +0,0 @@ -# Copyright (c) 2017 Red Hat, Inc. -# -# This software is licensed to you under the GNU General Public License, -# version 2 (GPLv2). There is NO WARRANTY for this software, express or -# implied, including the implied warranties of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -# along with this software; if not, see -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -# -# Red Hat trademarks are not licensed under GPLv2. No permission is -# granted to use or replicate Red Hat trademarks that are incorporated -# in this software or its documentation. -from unittest import mock - -from test.rhsmlib.base import InjectionMockingTest - -from subscription_manager import injection as inj -from subscription_manager.identity import Identity -from subscription_manager.plugins import PluginManager - -from rhsm import connection - -from rhsmlib.services import attach - - -CONTENT_JSON = [ - { - "id": "19ec0d4f93ae47e18233b2590b3e71f3", - "consumer": { - "id": "8a8d01865d2cb201015d331b0078006a", - "uuid": "47680d96-cfa4-4326-b545-5a6e02a4e95a", - "name": "orgBConsumer-tZTbHviW", - "href": "/consumers/47680d96-cfa4-4326-b545-5a6e02a4e95a", - }, - "pool": { - "id": "8a8d01865d2cb201015d331b01b6006f", - "type": "NORMAL", - "owner": { - "id": "8a8d01865d2cb201015d331afec50059", - "key": "orgB-txDmAJWq", - "displayName": "orgB-txDmAJWq", - "href": "/owners/orgB-txDmAJWq", - }, - "activeSubscription": True, - "quantity": 1, - "startDate": "2017-07-11T19:23:14+0000", - "endDate": "2018-07-11T19:23:14+0000", - "attributes": [], - "consumed": 1, - "exported": 0, - "shared": 0, - "branding": [], - "calculatedAttributes": {"compliance_type": "Standard"}, - "productId": "prod-25G4r19T", - "productAttributes": [{"name": "type", "value": "SVC"}], - "derivedProductAttributes": [], - "productName": "prod-Fz0IBfN6", - "stacked": False, - "developmentPool": False, - "href": "/pools/8a8d01865d2cb201015d331b01b6006f", - "created": "2017-07-11T19:23:14+0000", - "updated": "2017-07-11T19:23:14+0000", - "providedProducts": [], - "derivedProvidedProducts": [], - "subscriptionId": "source_sub_-LO4l9YKv", - "subscriptionSubKey": "master", - }, - "certificates": [ - { - "key": "FAKE KEY", - "cert": "FAKE_CERT", - "serial": { - "id": 7020569423934353740, - "revoked": False, - "collected": False, - "expiration": "2018-07-11T19:23:14+0000", - "serial": 7020569423934353740, - "created": "2017-07-11T19:23:14+0000", - "updated": "2017-07-11T19:23:14+0000", - }, - "id": "8a8d01865d2cb201015d331b02870072", - "created": "2017-07-11T19:23:14+0000", - "updated": "2017-07-11T19:23:14+0000", - } - ], - "quantity": 1, - "startDate": "2017-07-11T19:23:14+0000", - "endDate": "2018-07-11T19:23:14+0000", - "href": "/entitlements/19ec0d4f93ae47e18233b2590b3e71f3", - "created": "2017-07-11T19:23:14+0000", - "updated": "2017-07-11T19:23:14+0000", - } -] - - -class TestAttachService(InjectionMockingTest): - def setUp(self): - super(TestAttachService, self).setUp() - self.mock_identity = mock.Mock(spec=Identity, name="Identity").return_value - self.mock_cp = mock.Mock(spec=connection.UEPConnection, name="UEPConnection").return_value - self.mock_pm = mock.Mock(spec=PluginManager, name="PluginManager").return_value - - def injection_definitions(self, *args, **kwargs): - if args[0] == inj.IDENTITY: - return self.mock_identity - elif args[0] == inj.PLUGIN_MANAGER: - return self.mock_pm - else: - return None - - def test_pool_attach(self): - self.mock_identity.is_valid.return_value = True - self.mock_identity.uuid = "id" - - self.mock_cp.bindByEntitlementPool.return_value = CONTENT_JSON - - result = attach.AttachService(self.mock_cp).attach_pool("x", 1) - - self.assertEqual(CONTENT_JSON, result) - - expected_bind_calls = [ - mock.call("id", "x", 1), - ] - self.assertEqual(expected_bind_calls, self.mock_cp.bindByEntitlementPool.call_args_list) - - expected_plugin_calls = [ - mock.call("pre_subscribe", consumer_uuid="id", pool_id="x", quantity=1), - mock.call("post_subscribe", consumer_uuid="id", entitlement_data=CONTENT_JSON), - ] - self.assertEqual(expected_plugin_calls, self.mock_pm.run.call_args_list) - - def test_auto_attach(self): - self.mock_identity.is_valid.return_value = True - self.mock_identity.uuid = "id" - - self.mock_cp.bind.return_value = CONTENT_JSON - - result = attach.AttachService(self.mock_cp).attach_auto("service_level") - self.assertEqual(CONTENT_JSON, result) - - expected_update_calls = [mock.call("id", service_level="service_level")] - self.assertEqual(expected_update_calls, self.mock_cp.updateConsumer.call_args_list) - - expected_bind_calls = [ - mock.call("id"), - ] - self.assertEqual(expected_bind_calls, self.mock_cp.bind.call_args_list) - - expected_plugin_calls = [ - mock.call("pre_auto_attach", consumer_uuid="id"), - mock.call("post_auto_attach", consumer_uuid="id", entitlement_data=CONTENT_JSON), - ] - self.assertEqual(expected_plugin_calls, self.mock_pm.run.call_args_list) diff --git a/test/rhsmlib/services/test_entitlement.py b/test/rhsmlib/services/test_entitlement.py index 21beb85fbe..0c5a754ef2 100644 --- a/test/rhsmlib/services/test_entitlement.py +++ b/test/rhsmlib/services/test_entitlement.py @@ -289,172 +289,6 @@ def test_no_pool_with_specified_filter(self, mock_managerlib): filtered = service.get_available_pools(service_level="NotFound") self.assertEqual(0, len(filtered)) - def test_remove_all_pools(self): - """ - Test of removing all pools - """ - ent_service = EntitlementService(self.mock_cp) - ent_service.entcertlib = mock.Mock().return_value - ent_service.entcertlib.update = mock.Mock() - ent_service.cp.unbindAll = mock.Mock(return_value="[]") - - response = ent_service.remove_all_entitlements() - self.assertEqual(response, "[]") - - def test_remove_all_pools_by_id(self): - """ - Test of removing all pools by IDs of pool - """ - ent_service = EntitlementService(self.mock_cp) - ent_service.cp.unbindByPoolId = mock.Mock() - ent_service.entitlement_dir.list_serials_for_pool_ids = mock.Mock( - return_value={ - "4028fa7a5dea087d015dea0b025003f6": ["6219625278114868779"], - "4028fa7a5dea087d015dea0adf560152": ["3573249574655121394"], - } - ) - ent_service.entcertlib = mock.Mock().return_value - ent_service.entcertlib.update = mock.Mock() - - removed_pools, unremoved_pools, removed_serials = ent_service.remove_entitlements_by_pool_ids( - ["4028fa7a5dea087d015dea0b025003f6", "4028fa7a5dea087d015dea0adf560152"] - ) - - expected_removed_serials = ["6219625278114868779", "3573249574655121394"] - expected_removed_pools = ["4028fa7a5dea087d015dea0b025003f6", "4028fa7a5dea087d015dea0adf560152"] - - self.assertEqual(expected_removed_serials, removed_serials) - self.assertEqual(expected_removed_pools, removed_pools) - self.assertEqual([], unremoved_pools) - - def test_remove_dupli_pools_by_id(self): - """ - Test of removing pools specified with duplicities - (one pool id is set twice) - """ - ent_service = EntitlementService(self.mock_cp) - ent_service.cp.unbindByPoolId = mock.Mock() - ent_service.entitlement_dir.list_serials_for_pool_ids = mock.Mock( - return_value={ - "4028fa7a5dea087d015dea0b025003f6": ["6219625278114868779"], - "4028fa7a5dea087d015dea0adf560152": ["3573249574655121394"], - } - ) - ent_service.entcertlib = mock.Mock().return_value - ent_service.entcertlib.update = mock.Mock() - - removed_pools, unremoved_pools, removed_serials = ent_service.remove_entitlements_by_pool_ids( - [ - "4028fa7a5dea087d015dea0b025003f6", - "4028fa7a5dea087d015dea0b025003f6", - "4028fa7a5dea087d015dea0adf560152", - ] - ) - - expected_removed_serials = ["6219625278114868779", "3573249574655121394"] - expected_removed_pools = ["4028fa7a5dea087d015dea0b025003f6", "4028fa7a5dea087d015dea0adf560152"] - - self.assertEqual(expected_removed_serials, removed_serials) - self.assertEqual(expected_removed_pools, removed_pools) - self.assertEqual([], unremoved_pools) - - def test_remove_some_pools_by_id(self): - """ - Test of removing only some pools, because one pool ID is not valid - """ - ent_service = EntitlementService(self.mock_cp) - - def stub_unbind(uuid, pool_id): - if pool_id == "does_not_exist_d015dea0adf560152": - raise connection.RestlibException(400, "Error") - - ent_service.cp.unbindByPoolId = mock.Mock(side_effect=stub_unbind) - ent_service.entitlement_dir.list_serials_for_pool_ids = mock.Mock( - return_value={ - "4028fa7a5dea087d015dea0b025003f6": ["6219625278114868779"], - "4028fa7a5dea087d015dea0adf560152": ["3573249574655121394"], - } - ) - ent_service.entcertlib = mock.Mock().return_value - ent_service.entcertlib.update = mock.Mock() - - removed_pools, unremoved_pools, removed_serials = ent_service.remove_entitlements_by_pool_ids( - ["4028fa7a5dea087d015dea0b025003f6", "does_not_exist_d015dea0adf560152"] - ) - - expected_removed_serials = ["6219625278114868779"] - expected_removed_pools = ["4028fa7a5dea087d015dea0b025003f6"] - expected_unremoved_pools = ["does_not_exist_d015dea0adf560152"] - - self.assertEqual(expected_removed_serials, removed_serials) - self.assertEqual(expected_removed_pools, removed_pools) - self.assertEqual(expected_unremoved_pools, unremoved_pools) - - def test_remove_all_pools_by_serial(self): - """ - Test of removing all pools by serial numbers - """ - ent_service = EntitlementService(self.mock_cp) - ent_service.cp.unbindBySerial = mock.Mock() - - ent_service.entcertlib = mock.Mock().return_value - ent_service.entcertlib.update = mock.Mock() - - removed_serial, unremoved_serials = ent_service.remove_entitlements_by_serials( - ["6219625278114868779", "3573249574655121394"] - ) - - expected_removed_serials = ["6219625278114868779", "3573249574655121394"] - - self.assertEqual(expected_removed_serials, removed_serial) - self.assertEqual([], unremoved_serials) - - def test_remove_dupli_pools_by_serial(self): - """ - Test of removing pools specified with duplicities - (one serial number is set twice) - """ - ent_service = EntitlementService(self.mock_cp) - ent_service.cp.unbindBySerial = mock.Mock() - - ent_service.entcertlib = mock.Mock().return_value - ent_service.entcertlib.update = mock.Mock() - - removed_serial, unremoved_serials = ent_service.remove_entitlements_by_serials( - ["6219625278114868779", "6219625278114868779", "3573249574655121394"] - ) - - expected_removed_serials = ["6219625278114868779", "3573249574655121394"] - - self.assertEqual(expected_removed_serials, removed_serial) - self.assertEqual([], unremoved_serials) - - def test_remove_some_pools_by_serial(self): - """ - Test of removing some of pools by serial numbers, because one serial - number is not valid. - """ - ent_service = EntitlementService(self.mock_cp) - - def stub_unbind(uuid, serial): - if serial == "does_not_exist_1394": - raise connection.RestlibException(400, "Error") - - ent_service.cp.unbindBySerial = mock.Mock(side_effect=stub_unbind) - - ent_service.entcertlib = mock.Mock().return_value - ent_service.entcertlib.update = mock.Mock() - - removed_serial, unremoved_serials = ent_service.remove_entitlements_by_serials( - ["6219625278114868779", "does_not_exist_1394"] - ) - - expected_removed_serials = ["6219625278114868779"] - expected_unremoved_serials = ["does_not_exist_1394"] - - self.assertEqual(expected_removed_serials, removed_serial) - self.assertEqual(expected_unremoved_serials, unremoved_serials) - def test_parse_valid_date(self): """ Test parsing valid date diff --git a/test/rhsmlib/services/test_environment.py b/test/rhsmlib/services/test_environment.py new file mode 100644 index 0000000000..1abc418ba9 --- /dev/null +++ b/test/rhsmlib/services/test_environment.py @@ -0,0 +1,77 @@ +# Copyright (c) 2024 Red Hat, Inc. +# +# This software is licensed to you under the GNU General Public License, +# version 2 (GPLv2). There is NO WARRANTY for this software, express or +# implied, including the implied warranties of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 +# along with this software; if not, see +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +# +# Red Hat trademarks are not licensed under GPLv2. No permission is +# granted to use or replicate Red Hat trademarks that are incorporated +# in this software or its documentation. +from unittest import mock + +from test.rhsmlib.base import InjectionMockingTest + +from rhsm import connection + +from rhsmlib.services import environment + + +ENVIRONMENTS_JSON = [ + { + "created": "2024-10-03T18:12:56+0000", + "updated": "2024-10-03T18:12:56+0000", + "id": "8bdf14cf9e534119a1fe617c03304768", + "name": "template 1", + "type": "content-template", + "description": "my template", + "owner": { + "id": "8ad980939253781c01925378340e0002", + "key": "content-sources-test", + "displayName": "ContentSourcesTest", + "href": "/owners/content-sources-test", + "contentAccessMode": "org_environment", + }, + "environmentContent": [ + {"contentId": "11055", "enabled": True}, + {"contentId": "56a3a98c76ea4e16bd68424a2c9cc1c1", "enabled": True}, + {"contentId": "11049", "enabled": True}, + ], + }, + { + "created": "2024-10-09T19:08:14+0000", + "updated": "2024-10-09T19:08:14+0000", + "id": "6c62889601be41128fe2fece53141fd4", + "name": "template 2", + "type": "content-template", + "description": "my template", + "owner": { + "id": "8ad980939253781c01925378340e0002", + "key": "content-sources-test", + "displayName": "ContentSourcesTest", + "href": "/owners/content-sources-test", + "contentAccessMode": "org_environment", + }, + "environmentContent": [ + {"contentId": "11055", "enabled": True}, + {"contentId": "11049", "enabled": True}, + ], + }, +] + + +class TestEnvironmentService(InjectionMockingTest): + def setUp(self): + super(TestEnvironmentService, self).setUp() + self.mock_cp = mock.Mock(spec=connection.UEPConnection, name="UEPConnection") + + def injection_definitions(self, *args, **kwargs): + return None + + def test_list_environments(self): + self.mock_cp.getEnvironmentList.return_value = ENVIRONMENTS_JSON + + result = environment.EnvironmentService(self.mock_cp).list("org") + self.assertEqual(ENVIRONMENTS_JSON, result) diff --git a/test/rhsmlib/services/test_register.py b/test/rhsmlib/services/test_register.py index e4ee60c620..e047a81fb9 100644 --- a/test/rhsmlib/services/test_register.py +++ b/test/rhsmlib/services/test_register.py @@ -16,7 +16,7 @@ import subscription_manager.injection as inj -from subscription_manager.cache import InstalledProductsManager, ContentAccessModeCache +from subscription_manager.cache import InstalledProductsManager from subscription_manager.cp_provider import CPProvider from subscription_manager.facts import Facts from subscription_manager.identity import Identity @@ -54,20 +54,163 @@ "displayName": "Admin Owner", "id": "ff808081550d997c01550d9adaf40003", "key": "admin", - "contentAccessMode": "entitlement" + "contentAccessMode": "org_environment" }, "href": "/consumers/c1b8648c-6f0a-4aa5-b34e-b9e62c0e4364", "facts": {}, "id": "ff808081550d997c015511b0406d1065", "uuid": "c1b8648c-6f0a-4aa5-b34e-b9e62c0e4364", "guestIds": null, "capabilities": null, - "environment": null, "installedProducts": null, + "environment": { + "created" : "2024-12-09T09:25:17+0000", + "updated" : "2024-12-09T09:25:17+0000", + "id" : "env-id-1", + "name" : "env-name-1", + "type" : "content-template", + "description" : "Testing environment #1", + "contentPrefix" : null, + "owner" : { + "id" : "ff808081550d997c01550d9adaf40003", + "key" : "admin", + "displayName" : "Admin Owner", + "href" : "/owners/admin", + "contentAccessMode" : "org_environment" + }, + "environmentContent" : [ ] + }, + "installedProducts": null, "canActivate": false, "type": {"manifest": false, "id": "1000", "label": "system"}, "annotations": null, "username": "admin", "updated": "2016-06-02T15:16:51+0000", "lastCheckin": null, "entitlementCount": 0, "releaseVer": {"releaseVer": null}, "entitlementStatus": "valid", "name": "test.example.com", "created": "2016-06-02T15:16:51+0000", - "contentTags": null, "dev": false}""" + "contentTags": null, + "dev": false, + "environments": [ { + "created" : "2024-12-09T09:25:17+0000", + "updated" : "2024-12-09T09:25:17+0000", + "id" : "env-id-1", + "name" : "env-name-1", + "type" : "content-template", + "description" : "Testing environment #1", + "contentPrefix" : null, + "owner" : { + "id" : "ff808081550d997c01550d9adaf40003", + "key" : "admin", + "displayName" : "Admin Owner", + "href" : "/owners/admin", + "contentAccessMode" : "org_environment" + }, + "environmentContent" : [ ] + }, + { + "created" : "2024-12-09T09:25:17+0000", + "updated" : "2024-12-09T09:25:17+0000", + "id" : "env-id-2", + "name" : "env-name-2", + "type" : "content-template", + "description" : "Testing environment #2", + "contentPrefix" : null, + "owner" : { + "id" : "ff808081550d997c01550d9adaf40003", + "key" : "admin", + "displayName" : "Admin Owner", + "href" : "/owners/admin", + "contentAccessMode" : "org_environment" + }, + "environmentContent" : [ ] + } ] }""" + +CONSUMER_CONTENT_JSON_WRONG_ENT_TYPE = """{"hypervisorId": null, + "serviceLevel": "", + "autoheal": true, + "idCert": { + "key": "FAKE_KEY", + "cert": "FAKE_CERT", + "serial" : { + "id" : 5196045143213189102, + "revoked" : false, + "collected" : false, + "expiration" : "2033-04-25T18:03:06+0000", + "serial" : 5196045143213189102, + "created" : "2017-04-25T18:03:06+0000", + "updated" : "2017-04-25T18:03:06+0000" + }, + "id" : "8a8d011e5ba64700015ba647fbd20b88", + "created" : "2017-04-25T18:03:07+0000", + "updated" : "2017-04-25T18:03:07+0000" + }, + "owner": { + "href": "/owners/admin", + "displayName": "Admin Owner", + "id": "ff808081550d997c01550d9adaf40003", + "key": "admin", + "contentAccessMode": "org_environment" + }, + "href": "/consumers/c1b8648c-6f0a-4aa5-b34e-b9e62c0e4364", + "facts": {}, "id": "ff808081550d997c015511b0406d1065", + "uuid": "c1b8648c-6f0a-4aa5-b34e-b9e62c0e4364", + "guestIds": null, "capabilities": null, + "environment": { + "created" : "2024-12-09T09:25:17+0000", + "updated" : "2024-12-09T09:25:17+0000", + "id" : "env-id-1", + "name" : "env-name-1", + "type" : "content-template", + "description" : "Testing environment #1", + "contentPrefix" : null, + "owner" : { + "id" : "ff808081550d997c01550d9adaf40003", + "key" : "admin", + "displayName" : "Admin Owner", + "href" : "/owners/admin", + "contentAccessMode" : "org_environment" + }, + "environmentContent" : [ ] + }, + "installedProducts": null, + "canActivate": false, "type": {"manifest": false, + "id": "1000", "label": "system"}, "annotations": null, + "username": "admin", "updated": "2016-06-02T15:16:51+0000", + "lastCheckin": null, "entitlementCount": 0, "releaseVer": + {"releaseVer": null}, "entitlementStatus": "valid", "name": + "test.example.com", "created": "2016-06-02T15:16:51+0000", + "contentTags": null, + "dev": false, + "environments": [ { + "created" : "2024-12-09T09:25:17+0000", + "updated" : "2024-12-09T09:25:17+0000", + "id" : "env-id-1", + "name" : "env-name-1", + "type" : "content-template", + "description" : "Testing environment #1", + "contentPrefix" : null, + "owner" : { + "id" : "ff808081550d997c01550d9adaf40003", + "key" : "admin", + "displayName" : "Admin Owner", + "href" : "/owners/admin", + "contentAccessMode" : "org_environment" + }, + "environmentContent" : [ ] + }, + { + "created" : "2024-12-09T09:25:17+0000", + "updated" : "2024-12-09T09:25:17+0000", + "id" : "env-id-2", + "name" : "env-name-2", + "type" : "wrong_type_foo", + "description" : "Testing environment #2", + "contentPrefix" : null, + "owner" : { + "id" : "ff808081550d997c01550d9adaf40003", + "key" : "admin", + "displayName" : "Admin Owner", + "href" : "/owners/admin", + "contentAccessMode" : "org_environment" + }, + "environmentContent" : [ ] + } ] }""" # Following consumer do not contain information about content access mode OLD_CONSUMER_CONTENT_JSON = """{"hypervisorId": null, @@ -180,12 +323,6 @@ def setUp(self): # Add a mock cp_provider self.mock_cp_provider = mock.Mock(spec=CPProvider, name="CPProvider") - # Add a mock for content access mode cache - self.mock_content_access_mode_cache = mock.Mock( - spec=ContentAccessModeCache, name="ContentAccessModeCache" - ) - self.mock_content_access_mode_cache.read_data = mock.Mock(return_value="entitlement") - # For the tests in which it's used, the consumer_auth cp and basic_auth cp can be the same self.mock_cp_provider.get_consumer_auth_cp.return_value = self.mock_cp self.mock_cp_provider.get_basic_auth_cp.return_value = self.mock_cp @@ -215,8 +352,6 @@ def injection_definitions(self, *args, **kwargs): return self.mock_facts elif args[0] == inj.CP_PROVIDER: return self.mock_cp_provider - elif args[0] == inj.CONTENT_ACCESS_MODE_CACHE: - return self.mock_content_access_mode_cache else: return None @@ -230,13 +365,14 @@ def test_register_normally(self, mock_persist_consumer, mock_write_cache): self.mock_cp.registerConsumer.return_value = expected_consumer register_service = register.RegisterService(self.mock_cp) - register_service.register("org", name="name", environments="environment") + register_service.register("org", name="name", environments=["environment"]) self.mock_cp.registerConsumer.assert_called_once_with( name="name", facts={}, owner="org", - environments="environment", + environments=["environment"], + environment_names=None, keys=None, installed_products=[], jwt_token=None, @@ -259,25 +395,22 @@ def test_register_normally(self, mock_persist_consumer, mock_write_cache): @mock.patch("rhsmlib.services.register.syspurposelib.write_syspurpose_cache", return_value=True) @mock.patch("rhsmlib.services.register.managerlib.persist_consumer_cert") - def test_register_normally_old_candlepin(self, mock_persist_consumer, mock_write_cache): - """ - Test for the case, when candlepin server returns consumer without information about - content access mode. - """ + def test_register_multiple_environment_ids(self, mock_persist_consumer, mock_write_cache): self.mock_identity.is_valid.return_value = False self.mock_installed_products.format_for_server.return_value = [] self.mock_installed_products.tags = [] - expected_consumer = json.loads(OLD_CONSUMER_CONTENT_JSON) + expected_consumer = json.loads(CONSUMER_CONTENT_JSON) self.mock_cp.registerConsumer.return_value = expected_consumer register_service = register.RegisterService(self.mock_cp) - consumer = register_service.register("org", name="name", environments="environment") + register_service.register("org", name="name", environments=["env-id-1", "env-id-2"]) self.mock_cp.registerConsumer.assert_called_once_with( name="name", facts={}, owner="org", - environments="environment", + environments=["env-id-1", "env-id-2"], + environment_names=None, keys=None, installed_products=[], jwt_token=None, @@ -297,31 +430,25 @@ def test_register_normally_old_candlepin(self, mock_persist_consumer, mock_write mock.call("post_register_consumer", consumer=expected_consumer, facts={}), ] self.assertEqual(expected_plugin_calls, self.mock_pm.run.call_args_list) - assert "owner" in consumer - assert "contentAccessMode" in consumer["owner"] - assert "entitlement" == consumer["owner"]["contentAccessMode"] @mock.patch("rhsmlib.services.register.syspurposelib.write_syspurpose_cache", return_value=True) @mock.patch("rhsmlib.services.register.managerlib.persist_consumer_cert") - def test_register_normally_no_owner(self, mock_persist_consumer, mock_write_cache): - """ - Test for the case, when candlepin server returns consumer without owner - """ + def test_register_multiple_environment_names(self, mock_persist_consumer, mock_write_cache): self.mock_identity.is_valid.return_value = False self.mock_installed_products.format_for_server.return_value = [] self.mock_installed_products.tags = [] - expected_consumer = json.loads(OLD_CONSUMER_CONTENT_JSON) - del expected_consumer["owner"] + expected_consumer = json.loads(CONSUMER_CONTENT_JSON) self.mock_cp.registerConsumer.return_value = expected_consumer register_service = register.RegisterService(self.mock_cp) - consumer = register_service.register("org", name="name", environments="environment") + register_service.register("org", name="name", environment_names=["env-name-1", "env-name-2"]) self.mock_cp.registerConsumer.assert_called_once_with( name="name", facts={}, owner="org", - environments="environment", + environments=None, + environment_names=["env-name-1", "env-name-2"], keys=None, installed_products=[], jwt_token=None, @@ -341,7 +468,132 @@ def test_register_normally_no_owner(self, mock_persist_consumer, mock_write_cach mock.call("post_register_consumer", consumer=expected_consumer, facts={}), ] self.assertEqual(expected_plugin_calls, self.mock_pm.run.call_args_list) - assert "owner" not in consumer + + @mock.patch("rhsmlib.services.register.syspurposelib.write_syspurpose_cache", return_value=True) + @mock.patch("rhsmlib.services.register.managerlib.persist_consumer_cert") + def test_register_environment_name_type(self, mock_persist_consumer, mock_write_cache): + self.mock_identity.is_valid.return_value = False + self.mock_installed_products.format_for_server.return_value = [] + self.mock_installed_products.tags = [] + expected_consumer = json.loads(CONSUMER_CONTENT_JSON) + self.mock_cp.registerConsumer.return_value = expected_consumer + + register_service = register.RegisterService(self.mock_cp) + register_service.register( + "org", + name="name", + environment_names=["env-name-1", "env-name-2"], + environment_type="content-template", + ) + + self.mock_cp.registerConsumer.assert_called_once_with( + name="name", + facts={}, + owner="org", + environments=None, + environment_names=["env-name-1", "env-name-2"], + keys=None, + installed_products=[], + jwt_token=None, + content_tags=[], + consumer_type="system", + role="", + addons=[], + service_level="", + usage="", + ) + self.mock_installed_products.write_cache.assert_called() + + mock_persist_consumer.assert_called_once_with(expected_consumer) + mock_write_cache.assert_called_once() + expected_plugin_calls = [ + mock.call("pre_register_consumer", name="name", facts={}), + mock.call("post_register_consumer", consumer=expected_consumer, facts={}), + ] + self.assertEqual(expected_plugin_calls, self.mock_pm.run.call_args_list) + + @mock.patch("rhsmlib.services.register.syspurposelib.write_syspurpose_cache", return_value=True) + @mock.patch("rhsmlib.services.register.managerlib.persist_consumer_cert") + def test_register_environment_name_wrong_type(self, mock_persist_consumer, mock_write_cache): + self.mock_identity.is_valid.return_value = False + self.mock_installed_products.format_for_server.return_value = [] + self.mock_installed_products.tags = [] + expected_consumer = json.loads(CONSUMER_CONTENT_JSON_WRONG_ENT_TYPE) + self.mock_cp.registerConsumer.return_value = expected_consumer + + register_service = register.RegisterService(self.mock_cp) + + with self.assertRaises(Exception): + register_service.register( + "org", + name="name", + environment_names=["env-name-1", "env-name-2"], + environment_type="content-template", + ) + + @mock.patch("rhsmlib.services.register.syspurposelib.write_syspurpose_cache", return_value=True) + @mock.patch("rhsmlib.services.register.managerlib.persist_consumer_cert") + def test_register_not_allow_environment_ids_and_names(self, mock_persist_consumer, mock_write_cache): + self.mock_identity.is_valid.return_value = False + self.mock_installed_products.format_for_server.return_value = [] + self.mock_installed_products.tags = [] + expected_consumer = json.loads(CONSUMER_CONTENT_JSON) + self.mock_cp.registerConsumer.return_value = expected_consumer + + register_service = register.RegisterService(self.mock_cp) + with self.assertRaisesRegex( + exceptions.ValidationError, r".*Environment IDs and environment names are mutually exclusive.*" + ): + register_service.register( + "org", + name="name", + environments=["env-id-1", "env-id-2"], + environment_names=["env-name-1", "env-name-2"], + ) + + @mock.patch("rhsmlib.services.register.syspurposelib.write_syspurpose_cache", return_value=True) + @mock.patch("rhsmlib.services.register.managerlib.clean_all_data", return_value=None) + @mock.patch("rhsmlib.services.register.managerlib.persist_consumer_cert") + def test_register_normally_old_candlepin(self, mock_persist_consumer, mock_clean, mock_write_cache): + """ + Test for the case, when candlepin server returns consumer without information about + content access mode. + """ + self.mock_identity.is_valid.return_value = False + self.mock_installed_products.format_for_server.return_value = [] + self.mock_installed_products.tags = [] + expected_consumer = json.loads(OLD_CONSUMER_CONTENT_JSON) + self.mock_cp.registerConsumer.return_value = expected_consumer + + register_service = register.RegisterService(self.mock_cp) + with self.assertRaises(exceptions.ServiceError) as exc_info: + register_service.register("org", name="name", environments=["environment"]) + self.assertEqual( + str(exc_info.exception), + "Registration is only possible when the organization is in Simple Content Access Mode.", + ) + + @mock.patch("rhsmlib.services.register.syspurposelib.write_syspurpose_cache", return_value=True) + @mock.patch("rhsmlib.services.register.managerlib.clean_all_data") + @mock.patch("rhsmlib.services.register.managerlib.persist_consumer_cert") + def test_register_normally_no_owner(self, mock_persist_consumer, mock_clean, mock_write_cache): + """ + Test for the case, when candlepin server returns consumer without owner + """ + self.mock_identity.is_valid.return_value = False + self.mock_installed_products.format_for_server.return_value = [] + self.mock_installed_products.tags = [] + expected_consumer = json.loads(OLD_CONSUMER_CONTENT_JSON) + del expected_consumer["owner"] + self.mock_cp.registerConsumer.return_value = expected_consumer + + register_service = register.RegisterService(self.mock_cp) + with self.assertRaises(exceptions.ServiceError) as exc_info: + register_service.register("org", name="name", environments=["environment"]) + self.assertEqual( + str(exc_info.exception), + "Registration is only possible when the organization is in Simple Content Access Mode.", + ) @mock.patch("rhsmlib.services.register.syspurposelib.write_syspurpose_cache", return_value=True) @mock.patch("rhsmlib.services.register.managerlib.persist_consumer_cert") @@ -391,13 +643,14 @@ def _no_owner_cb(username): self.assertIsNotNone(org) - register_service.register(org, name="name", environments="environment") + register_service.register(org, name="name") self.mock_cp.registerConsumer.assert_called_once_with( name="name", facts={}, owner="snowwhite", - environments="environment", + environments=None, + environment_names=None, keys=None, installed_products=[], jwt_token=None, @@ -438,6 +691,7 @@ def test_register_with_activation_keys(self, mock_persist_consumer, mock_write_c facts={}, owner="org", environments=None, + environment_names=None, keys=[1], installed_products=[], jwt_token=None, @@ -523,6 +777,7 @@ def test_reads_syspurpose(self, mock_persist_consumer, mock_write_cache): addons=["addon1"], content_tags=[], environments=None, + environment_names=None, facts={}, installed_products=[], jwt_token=None, @@ -550,15 +805,6 @@ def test_does_not_allow_basic_auth_with_activation_keys(self): with self.assertRaisesRegex(exceptions.ValidationError, r".*do not require user credentials.*"): register.RegisterService(self.mock_cp).validate_options(options) - def test_does_not_allow_environment_with_activation_keys(self): - self.mock_cp.username = None - self.mock_cp.password = None - - self.mock_identity.is_valid.return_value = False - options = self._build_options(activation_keys=[1], environments="environment") - with self.assertRaisesRegex(exceptions.ValidationError, r".*do not allow environments.*"): - register.RegisterService(self.mock_cp).validate_options(options) - def test_does_not_allow_environment_with_consumerid(self): self.mock_cp.username = None self.mock_cp.password = None diff --git a/test/smoke.sh b/test/smoke.sh index 9130825723..eb8e9a5b34 100755 --- a/test/smoke.sh +++ b/test/smoke.sh @@ -226,7 +226,6 @@ run_sm "0" list --available run_sm "0" service-level run_sm "0" service-level --list run_sm "0" repos -run_sm "0" attach # Note: with current test data, the awesome-os repos will never be enabled run_yum "0" repolist @@ -282,7 +281,6 @@ run_rhsmcertd "0" run_rhsmcertd "0" -n run_rhsmcertd_worker "0" -run_rhsmcertd_worker "0" --autoheal # too slow # run_rhsm_debug "0" system @@ -319,8 +317,6 @@ run_sm "0" repos --list # fully entitled, hence the '1' run_sm "1" register --activationkey "${ACTIVATION_KEY}" --org "${ORG}" --force run_sm "0" unregister -run_sm "64" register --activationkey "${ACTIVATION_KEY}" --org "${ORG}" --force --auto-attach -run_sm "1" unregister run_sm "0" clean diff --git a/test/stubs.py b/test/stubs.py index 527f10571e..b595c87c9c 100644 --- a/test/stubs.py +++ b/test/stubs.py @@ -32,7 +32,6 @@ AvailableEntitlementsCache, SyspurposeValidFieldsCache, CurrentOwnerCache, - ContentAccessModeCache, SyspurposeComplianceStatusCache, ) from subscription_manager.facts import Facts @@ -483,8 +482,6 @@ def __init__( self.environment_list = [] self.called_unregister_uuid = None self.called_unbind_uuid = None - self.called_unbind_serial = [] - self.called_unbind_pool_id = [] self.username = username self.password = password self._capabilities = [] @@ -495,8 +492,6 @@ def __init__( def reset(self): self.called_unregister_uuid = None self.called_unbind_uuid = None - self.called_unbind_serial = [] - self.called_unbind_pool_id = [] def has_capability(self, capability): return capability in self._capabilities @@ -536,7 +531,6 @@ def updateConsumer( guest_uuids=None, service_level=None, release=None, - autoheal=None, content_tags=None, addons=None, role=None, @@ -564,12 +558,6 @@ def getConsumer(self, consumerId): def unbindAll(self, consumer): self.called_unbind_uuid = consumer - def unbindBySerial(self, consumer, serial): - self.called_unbind_serial.append(serial) - - def unbindByPoolId(self, consumer_uuid, pool_id): - self.called_unbind_pool_id.append(pool_id) - def getCertificateSerials(self, consumer): return [] @@ -734,6 +722,9 @@ def get_no_auth_cp(self): def get_content_connection(self): return self.content_connection + def close_all_connections(self): + pass + class StubEntitlementStatusCache(EntitlementStatusCache): def write_cache(self, debug=False): @@ -783,14 +774,6 @@ def delete_cache(self): self.server_status = None -class StubContentAccessModeCache(ContentAccessModeCache): - def write_cache(self, debug=False): - pass - - def delete_cache(self): - self.server_status = None - - class StubSupportedResourcesCache(SupportedResourcesCache): def write_cache(self, debug=False): pass diff --git a/test/test_cache.py b/test/test_cache.py index 76640603fd..88354c3360 100644 --- a/test/test_cache.py +++ b/test/test_cache.py @@ -47,7 +47,6 @@ SupportedResourcesCache, AvailableEntitlementsCache, CurrentOwnerCache, - ContentAccessModeCache, SyspurposeComplianceStatusCache, ) @@ -1327,28 +1326,6 @@ def test_max_timeout(self): self.assertEqual(timeout, self.cache.UBOUND) -class TestContentAccessModeCache(SubManFixture): - MOCK_CACHE_FILE_CONTENT = '{"7f85da06-5c35-44ba-931d-f11f6e581f89": "entitlement"}' - - def setUp(self): - super(TestContentAccessModeCache, self).setUp() - self.cache = ContentAccessModeCache() - - def test_reading_nonexisting_cache(self): - data = self.cache.read_cache_only() - self.assertIsNone(data) - - def test_reading_existing_cache(self): - temp_cache_dir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, temp_cache_dir) - self.cache.CACHE_FILE = os.path.join(temp_cache_dir, "content_access_mode.json") - with open(self.cache.CACHE_FILE, "w") as cache_file: - cache_file.write(self.MOCK_CACHE_FILE_CONTENT) - data = self.cache.read_cache_only() - self.assertTrue("7f85da06-5c35-44ba-931d-f11f6e581f89" in data) - self.assertEqual(data["7f85da06-5c35-44ba-931d-f11f6e581f89"], "entitlement") - - class TestSyspurposeComplianceStatusCache(SubManFixture): def setUp(self): super(TestSyspurposeComplianceStatusCache, self).setUp() diff --git a/test/test_cert_sorter.py b/test/test_cert_sorter.py index 93d522142f..14a6979d93 100644 --- a/test/test_cert_sorter.py +++ b/test/test_cert_sorter.py @@ -63,8 +63,9 @@ def stub_prod_cert(pid): class CertSorterTests(SubManFixture): + @patch("subscription_manager.cert_sorter.utils.is_simple_content_access") @patch("subscription_manager.cache.InstalledProductsManager.update_check") - def setUp(self, mock_update): + def setUp(self, mock_update, mock_is_simple_content_access): SubManFixture.setUp(self) # Setup mock product and entitlement certs: self.prod_dir = StubProductDirectory(pids=[INST_PID_1, INST_PID_2, INST_PID_3, INST_PID_4]) @@ -79,6 +80,7 @@ def setUp(self, mock_update): ), ] ) + mock_is_simple_content_access.return_value = False self.mock_uep = StubUEP() @@ -170,11 +172,13 @@ def test_installed_mismatch_unentitled(self, mock_update): # server reported it here: self.assertFalse(INST_PID_3 in sorter.unentitled_products) + @patch("subscription_manager.cert_sorter.utils.is_simple_content_access") @patch("subscription_manager.cache.InstalledProductsManager.update_check") - def test_missing_installed_product(self, mock_update): + def test_missing_installed_product(self, mock_update, mock_is_simple_content_access): # Add a new installed product server doesn't know about: prod_dir = StubProductDirectory(pids=[INST_PID_1, INST_PID_2, INST_PID_3, "product4"]) inj.provide(inj.PROD_DIR, prod_dir) + mock_is_simple_content_access.return_value = False sorter = CertSorter() self.assertTrue("product4" in sorter.unentitled_products) diff --git a/test/test_certmgr.py b/test/test_certmgr.py index 3f64841d23..7c5013706e 100644 --- a/test/test_certmgr.py +++ b/test/test_certmgr.py @@ -30,7 +30,6 @@ from rhsm.profile import RPMProfile from rhsm.connection import GoneException -from rhsm.certificate import GMT from .fixture import SubManFixture, set_up_mock_sp_store @@ -39,7 +38,7 @@ "releaseVer": {"id": 1, "releaseVer": "123123"}, "serviceLevel": "Pro Turbo HD Plus Ultra", "owner": {"key": "admin"}, - "autoheal": 1, + "autoheal": True, "idCert": {"serial": {"serial": 3787455826750723380}}, } @@ -280,47 +279,3 @@ def test_exception_on_cert_write(self, mock_log, mock_cert_build): if call[0] == "exception" and isinstance(call[1][0], ExceptionalException): return self.fail("Did not ExceptionException in the logged exceptions") - - -class TestHealingActionClient(TestActionClient): - def test_healing_no_heal(self): - self.mock_cert_sorter.is_valid = mock.Mock(return_value=True) - self.mock_cert_sorter.compliant_until = datetime.now() + timedelta(days=15) - actionclient = action_client.HealingActionClient() - actionclient.update() - self.assertFalse(self.mock_uep.bind.called) - - def test_healing_needs_heal(self): - # need a stub product dir with prods with no entitlements, - # don't have to mock here since we can actually pass in a product - self.mock_cert_sorter.is_valid = mock.Mock(return_value=False) - actionclient = action_client.HealingActionClient() - actionclient.update() - self.assertTrue(self.mock_uep.bind.called) - - @mock.patch.object(entcertlib.EntitlementCertBundleInstaller, "build_cert") - def test_healing_needs_heal_tomorrow(self, cert_build_mock): - # Valid today, but not valid 24h from now: - self.mock_cert_sorter.is_valid = mock.Mock(return_value=True) - self.mock_cert_sorter.compliant_until = datetime.now(GMT()) + timedelta(hours=6) - cert_build_mock.return_value = (mock.Mock(), self.stub_ent_expires_tomorrow) - - self._stub_certificate_calls([self.stub_ent_expires_tomorrow]) - actionclient = action_client.HealingActionClient() - actionclient.update() - # see if we tried to update certs - self.assertTrue(self.mock_uep.bind.called) - - # TODO: use Mock(wraps=) instead of hiding all logging - @mock.patch("subscription_manager.healinglib.log") - def test_healing_trigger_exception(self, mock_log): - # Forcing is_valid to throw the type error we used to expect from - # cert sorter using the product dir. Just making sure an unexpected - # exception is logged and not bubbling up. - self.mock_cert_sorter.is_valid = mock.Mock(side_effect=TypeError()) - actionclient = action_client.HealingActionClient() - actionclient.update() - for call in mock_log.method_calls: - if call[0] == "exception" and isinstance(call[1][0], TypeError): - return - self.fail("Did not see TypeError in the logged exceptions") diff --git a/test/test_healinglib.py b/test/test_healinglib.py deleted file mode 100644 index 044447cd5d..0000000000 --- a/test/test_healinglib.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright (c) 2013 Red Hat, Inc. -# -# This software is licensed to you under the GNU General Public License, -# version 2 (GPLv2). There is NO WARRANTY for this software, express or -# implied, including the implied warranties of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -# along with this software; if not, see -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -# -# Red Hat trademarks are not licensed under GPLv2. No permission is -# granted to use or replicate Red Hat trademarks that are incorporated -# in this software or its documentation. -# - -from unittest import mock - -from . import fixture - -from subscription_manager import healinglib - - -class TestHealingActionInvoker(fixture.SubManFixture): - def setUp(self): - super(TestHealingActionInvoker, self).setUp() - - def test_autoheal_off(self): - mock_uep = mock.Mock() - mock_uep.getConsumer = mock.Mock(return_value=self._consumer()) - self.set_consumer_auth_cp(mock_uep) - - hl = healinglib.HealingActionInvoker() - hl.update() - - def test_autoheal_on(self): - mock_uep = mock.Mock() - consumer = {"autoheal": True} - mock_uep.getConsumer = mock.Mock(return_value=consumer) - self.set_consumer_auth_cp(mock_uep) - - hl = healinglib.HealingActionInvoker() - report = hl.update() - report.print_exceptions() - - def _consumer(self): - consumer = {} - return consumer - - -# FIXME: assert something -class TestHealingUpdateAction(fixture.SubManFixture): - # HealingLib is very thin wrapper to HealingUpdateAction atm, - # so basically the same tests - def setUp(self): - super(TestHealingUpdateAction, self).setUp() - - def test_autoheal_off(self): - mock_uep = mock.Mock() - # nothing set on consumer - consumer = {} - mock_uep.getConsumer = mock.Mock(return_value=consumer) - self.set_consumer_auth_cp(mock_uep) - - hl = healinglib.HealingUpdateAction() - hl.perform() - - def test_autoheal_on(self): - mock_uep = mock.Mock() - consumer = {"autoheal": True} - mock_uep.getConsumer = mock.Mock(return_value=consumer) - self.set_consumer_auth_cp(mock_uep) - - hl = healinglib.HealingUpdateAction() - hl.perform() diff --git a/test/test_managercli.py b/test/test_managercli.py index 9b921a459f..1412c266b0 100644 --- a/test/test_managercli.py +++ b/test/test_managercli.py @@ -212,7 +212,7 @@ def test_unknown_args_cause_exit(self): sys, "argv", # test with some subcommand; sub-man prints help without it - ["subscription-manager", "attach", "--foo", "bar", "baz"], + ["subscription-manager", "register", "--foo", "bar", "baz"], ): try: self.cc.main() diff --git a/test/test_managerlib.py b/test/test_managerlib.py index f7ec64a526..975e0d067b 100644 --- a/test/test_managerlib.py +++ b/test/test_managerlib.py @@ -715,201 +715,6 @@ def MockSystemLog(self, message, priority): EXPECTED_CONTENT_V3 = EXPECTED_CERT_CONTENT_V3 + os.linesep + EXPECTED_KEY_CONTENT_V3 -class ExtractorStub(managerlib.ImportFileExtractor): - def __init__(self, content, file_path="test/file/path"): - self.content = content - self.writes = [] - managerlib.ImportFileExtractor.__init__(self, file_path) - - # Stub out any file system access - def _read(self, file_path): - return self.content - - def _write_file(self, target, content): - self.writes.append((target, content)) - - def _ensure_entitlement_dir_exists(self): - # Do nothing but stub out the dir check to avoid file system access. - pass - - -class TestImportFileExtractor(unittest.TestCase): - def test_contains_key_content_when_key_and_cert_exists_in_import_file(self): - extractor = ExtractorStub(EXPECTED_CONTENT) - self.assertTrue(extractor.contains_key_content()) - - def test_contains_key_content_when_key_and_cert_exists_in_import_file_v3(self): - extractor = ExtractorStub(EXPECTED_CONTENT_V3) - self.assertTrue(extractor.contains_key_content()) - - def test_does_not_contain_key_when_key_does_not_exist_in_import_file(self): - extractor = ExtractorStub(EXPECTED_CERT_CONTENT) - self.assertFalse(extractor.contains_key_content()) - - def test_does_not_contain_key_when_key_does_not_exist_in_import_file_v3(self): - extractor = ExtractorStub(EXPECTED_CERT_CONTENT_V3) - self.assertFalse(extractor.contains_key_content()) - - def test_get_key_content_when_key_exists(self): - extractor = ExtractorStub(EXPECTED_CONTENT, file_path="12345.pem") - self.assertTrue(extractor.contains_key_content()) - self.assertEqual(EXPECTED_KEY_CONTENT, extractor.get_key_content()) - - def test_get_key_content_when_key_exists_v3(self): - extractor = ExtractorStub(EXPECTED_CONTENT_V3, file_path="12345.pem") - self.assertTrue(extractor.contains_key_content()) - self.assertEqual(EXPECTED_KEY_CONTENT_V3, extractor.get_key_content()) - - def test_get_key_content_returns_None_when_key_does_not_exist(self): - extractor = ExtractorStub(EXPECTED_CERT_CONTENT, file_path="12345.pem") - self.assertFalse(extractor.get_key_content()) - - def test_get_key_content_returns_None_when_key_does_not_exist_v3(self): - extractor = ExtractorStub(EXPECTED_CERT_CONTENT_V3, file_path="12345.pem") - self.assertFalse(extractor.get_key_content()) - - def test_get_cert_content(self): - extractor = ExtractorStub(EXPECTED_CONTENT, file_path="12345.pem") - self.assertTrue(extractor.contains_key_content()) - self.assertEqual(EXPECTED_CERT_CONTENT, extractor.get_cert_content()) - - def test_get_cert_content_v3(self): - extractor = ExtractorStub(EXPECTED_CONTENT_V3, file_path="12345.pem") - self.assertTrue(extractor.contains_key_content()) - self.assertEqual(EXPECTED_CERT_CONTENT_V3, extractor.get_cert_content()) - - def test_get_cert_content_returns_None_when_cert_does_not_exist(self): - extractor = ExtractorStub(EXPECTED_KEY_CONTENT, file_path="12345.pem") - self.assertFalse(extractor.get_cert_content()) - - def test_get_cert_content_returns_None_when_cert_does_not_exist_v3(self): - extractor = ExtractorStub(EXPECTED_KEY_CONTENT_V3, file_path="12345.pem") - self.assertFalse(extractor.get_cert_content()) - - def test_verify_valid_entitlement_for_invalid_cert(self): - extractor = ExtractorStub(EXPECTED_KEY_CONTENT, file_path="12345.pem") - self.assertFalse(extractor.verify_valid_entitlement()) - - def test_verify_valid_entitlement_for_invalid_cert_v3(self): - extractor = ExtractorStub(EXPECTED_KEY_CONTENT_V3, file_path="12345.pem") - self.assertFalse(extractor.verify_valid_entitlement()) - - def test_verify_valid_entitlement_for_invalid_cert_bundle(self): - # Use a bundle of cert + key, but the cert is not an entitlement cert: - extractor = ExtractorStub(IDENTITY_CERT_WITH_KEY, file_path="12345.pem") - self.assertFalse(extractor.verify_valid_entitlement()) - - def test_verify_valid_entitlement_for_no_key(self): - extractor = ExtractorStub(EXPECTED_CERT_CONTENT, file_path="12345.pem") - self.assertFalse(extractor.verify_valid_entitlement()) - - def test_verify_valid_entitlement_for_no_key_v3(self): - extractor = ExtractorStub(EXPECTED_CERT_CONTENT_V3, file_path="12345.pem") - self.assertFalse(extractor.verify_valid_entitlement()) - - def test_verify_valid_entitlement_for_no_cert_content(self): - extractor = ExtractorStub("", file_path="12345.pem") - self.assertFalse(extractor.verify_valid_entitlement()) - - def test_write_cert_only(self): - expected_cert_file = "%d.pem" % (EXPECTED_CERT.serial) - extractor = ExtractorStub(EXPECTED_CERT_CONTENT, file_path=expected_cert_file) - extractor.write_to_disk() - - self.assertEqual(1, len(extractor.writes)) - - write_one = extractor.writes[0] - self.assertEqual(os.path.join(ENT_CONFIG_DIR, expected_cert_file), write_one[0]) - self.assertEqual(EXPECTED_CERT_CONTENT, write_one[1]) - - def test_write_cert_only_v3(self): - expected_cert_file = "%d.pem" % (EXPECTED_CERT_V3.serial) - extractor = ExtractorStub(EXPECTED_CERT_CONTENT_V3, file_path=expected_cert_file) - extractor.write_to_disk() - - self.assertEqual(1, len(extractor.writes)) - - write_one = extractor.writes[0] - self.assertEqual(os.path.join(ENT_CONFIG_DIR, expected_cert_file), write_one[0]) - self.assertEqual(EXPECTED_CERT_CONTENT_V3, write_one[1]) - - def test_write_key_and_cert(self): - filename = "%d.pem" % (EXPECTED_CERT.serial) - self._assert_correct_cert_and_key_files_generated_with_filename(filename) - - def test_write_key_and_cert_v3(self): - filename = "%d.pem" % (EXPECTED_CERT_V3.serial) - self._assert_correct_cert_and_key_files_generated_with_filename_v3(filename) - - def test_file_renamed_when_imported_with_serial_no_and_custom_extension(self): - filename = "%d.cert" % (EXPECTED_CERT.serial) - self._assert_correct_cert_and_key_files_generated_with_filename(filename) - - def test_file_renamed_when_imported_with_serial_no_and_custom_extension_v3(self): - filename = "%d.cert" % (EXPECTED_CERT_V3.serial) - self._assert_correct_cert_and_key_files_generated_with_filename_v3(filename) - - def test_file_renamed_when_imported_with_serial_no_and_no_extension(self): - filename = str(EXPECTED_CERT.serial) - self._assert_correct_cert_and_key_files_generated_with_filename(filename) - - def test_file_renamed_when_imported_with_serial_no_and_no_extension_v3(self): - filename = str(EXPECTED_CERT_V3.serial) - self._assert_correct_cert_and_key_files_generated_with_filename_v3(filename) - - def test_file_renamed_when_imported_with_custom_name_and_pem_extension(self): - filename = "entitlement.pem" - self._assert_correct_cert_and_key_files_generated_with_filename(filename) - - def test_file_renamed_when_imported_with_custom_name_and_pem_extension_v3(self): - filename = "entitlement.pem" - self._assert_correct_cert_and_key_files_generated_with_filename_v3(filename) - - def test_file_renamed_when_imported_with_custom_name_no_extension(self): - filename = "entitlement" - self._assert_correct_cert_and_key_files_generated_with_filename(filename) - - def test_file_renamed_when_imported_with_custom_name_no_extension_v3(self): - filename = "entitlement" - self._assert_correct_cert_and_key_files_generated_with_filename_v3(filename) - - def _assert_correct_cert_and_key_files_generated_with_filename(self, filename): - expected_file_prefix = "%d" % (EXPECTED_CERT.serial) - expected_cert_file = expected_file_prefix + ".pem" - expected_key_file = expected_file_prefix + "-key.pem" - - extractor = ExtractorStub(EXPECTED_CONTENT, file_path=filename) - extractor.write_to_disk() - - self.assertEqual(2, len(extractor.writes)) - - write_one = extractor.writes[0] - self.assertEqual(os.path.join(ENT_CONFIG_DIR, expected_cert_file), write_one[0]) - self.assertEqual(EXPECTED_CERT_CONTENT, write_one[1]) - - write_two = extractor.writes[1] - self.assertEqual(os.path.join(ENT_CONFIG_DIR, expected_key_file), write_two[0]) - self.assertEqual(EXPECTED_KEY_CONTENT, write_two[1]) - - def _assert_correct_cert_and_key_files_generated_with_filename_v3(self, filename): - expected_file_prefix = "%d" % (EXPECTED_CERT_V3.serial) - expected_cert_file = expected_file_prefix + ".pem" - expected_key_file = expected_file_prefix + "-key.pem" - - extractor = ExtractorStub(EXPECTED_CONTENT_V3, file_path=filename) - extractor.write_to_disk() - - self.assertEqual(2, len(extractor.writes)) - - write_one = extractor.writes[0] - self.assertEqual(os.path.join(ENT_CONFIG_DIR, expected_cert_file), write_one[0]) - self.assertEqual(EXPECTED_CERT_CONTENT_V3, write_one[1]) - - write_two = extractor.writes[1] - self.assertEqual(os.path.join(ENT_CONFIG_DIR, expected_key_file), write_two[0]) - self.assertEqual(EXPECTED_KEY_CONTENT_V3, write_two[1]) - - class TestMergedPoolsStackingGroupSorter(unittest.TestCase): def test_sorter_adds_group_for_non_stackable_entitlement(self): pool = self._create_pool("test-prod-1", "Test Prod 1") diff --git a/test/test_plugins.py b/test/test_plugins.py index e58c0827df..6a1fa9288f 100644 --- a/test/test_plugins.py +++ b/test/test_plugins.py @@ -1075,19 +1075,6 @@ def test_post_subscription_conduit(self): self.assertEqual({}, conduit.entitlement_data) -class TestAutoAttachConduit(unittest.TestCase): - def test_auto_attach_conduit(self): - conduit = plugins.AutoAttachConduit(StubPluginClass, "a-consumer-uuid") - self.assertEqual("a-consumer-uuid", conduit.consumer_uuid) - - -class TestPostAutoAttachConduit(unittest.TestCase): - def test_post_auto_attach_conduit(self): - conduit = plugins.PostAutoAttachConduit(StubPluginClass, "a-consumer-uuid", {}) - self.assertEqual("a-consumer-uuid", conduit.consumer_uuid) - self.assertEqual({}, conduit.entitlement_data) - - class BasePluginException(unittest.TestCase): """At least create and raise all the exceptions.""" diff --git a/test/test_printing_utils.py b/test/test_printing_utils.py index 69e02cff8c..2e208faccf 100644 --- a/test/test_printing_utils.py +++ b/test/test_printing_utils.py @@ -1,6 +1,5 @@ import unittest -from subscription_manager.cli_command.list import AVAILABLE_SUBS_MATCH_COLUMNS from subscription_manager.cli_command import status from subscription_manager.printing_utils import ( format_name, @@ -16,6 +15,15 @@ from unittest.mock import patch, Mock +TEST_COLUMNS = [ + "Subscription Name:", + "Provides:", + "SKU:", + "Contract:", + "Service Level:", +] + + class TestFormatName(unittest.TestCase): def setUp(self): self.indent = 1 @@ -66,7 +74,7 @@ def test_highlight_by_filter_string(self): args = ["Super Test Subscription"] kwargs = { "filter_string": "Super*", - "match_columns": AVAILABLE_SUBS_MATCH_COLUMNS, + "match_columns": TEST_COLUMNS, "caption": "Subscription Name:", "is_atty": True, } @@ -79,7 +87,7 @@ def test_highlight_by_filter_string_single(self): args = ["Super Test Subscription"] kwargs = { "filter_string": "*Subscriptio?", - "match_columns": AVAILABLE_SUBS_MATCH_COLUMNS, + "match_columns": TEST_COLUMNS, "caption": "Subscription Name:", "is_atty": True, } @@ -92,7 +100,7 @@ def test_highlight_by_filter_string_all(self): args = ["Super Test Subscription"] kwargs = { "filter_string": "*", - "match_columns": AVAILABLE_SUBS_MATCH_COLUMNS, + "match_columns": TEST_COLUMNS, "caption": "Subscription Name:", "is_atty": True, } @@ -103,7 +111,7 @@ def test_highlight_by_filter_string_exact(self): args = ["Premium"] kwargs = { "filter_string": "Premium", - "match_columns": AVAILABLE_SUBS_MATCH_COLUMNS, + "match_columns": TEST_COLUMNS, "caption": "Service Level:", "is_atty": True, } @@ -114,7 +122,7 @@ def test_highlight_by_filter_string_list_row(self): args = ["Awesome-os-stacked"] kwargs = { "filter_string": "Awesome*", - "match_columns": AVAILABLE_SUBS_MATCH_COLUMNS, + "match_columns": TEST_COLUMNS, "caption": "Subscription Name:", "is_atty": True, } diff --git a/test/test_remove.py b/test/test_remove.py deleted file mode 100644 index 38224e2f17..0000000000 --- a/test/test_remove.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright (c) 2012 Red Hat, Inc. -# -# This software is licensed to you under the GNU General Public License, -# version 2 (GPLv2). There is NO WARRANTY for this software, express or -# implied, including the implied warranties of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -# along with this software; if not, see -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -# -# Red Hat trademarks are not licensed under GPLv2. No permission is -# granted to use or replicate Red Hat trademarks that are incorporated -# in this software or its documentation. -# - -from subscription_manager import managercli -from subscription_manager import injection as inj - -from .stubs import ( - StubEntitlementDirectory, - StubProductDirectory, - StubEntActionInvoker, - StubEntitlementCertificate, - StubProduct, - StubPool, -) -from . import fixture - - -# This is a copy of CliUnSubscribeTests for the new name. -class CliRemoveTests(fixture.SubManFixture): - def test_unsubscribe_registered(self): - cmd = managercli.RemoveCommand() - - mock_identity = self._inject_mock_valid_consumer() - managercli.EntCertActionInvoker = StubEntActionInvoker - - cmd.main(["--all"]) - self.assertEqual(cmd.cp.called_unbind_uuid, mock_identity.uuid) - - serial1 = "123456" - cmd.main(["--serial=%s" % serial1]) - self.assertEqual(cmd.cp.called_unbind_serial, [serial1]) - cmd.cp.reset() - - serial2 = "789012" - cmd.main(["--serial=%s" % serial1, "--serial=%s" % serial2]) - self.assertEqual(cmd.cp.called_unbind_serial, [serial1, serial2]) - cmd.cp.reset() - - pool_id1 = "39993922b" - cmd.main( - ["--serial=%s" % serial1, "--serial=%s" % serial2, "--pool=%s" % pool_id1, "--pool=%s" % pool_id1] - ) - self.assertEqual(cmd.cp.called_unbind_serial, [serial1, serial2]) - self.assertEqual(cmd.cp.called_unbind_pool_id, [pool_id1]) - - def test_unsubscribe_unregistered(self): - prod = StubProduct("stub_product") - ent = StubEntitlementCertificate(prod) - - inj.provide(inj.ENT_DIR, StubEntitlementDirectory([ent])) - inj.provide(inj.PROD_DIR, StubProductDirectory([])) - cmd = managercli.RemoveCommand() - - self._inject_mock_invalid_consumer() - - cmd.main(["--all"]) - self.assertTrue(cmd.entitlement_dir.list_called) - self.assertTrue(ent.is_deleted) - - prod = StubProduct("stub_product") - pool = StubPool("stub_pool") - ent1 = StubEntitlementCertificate(prod) - ent2 = StubEntitlementCertificate(prod) - ent3 = StubEntitlementCertificate(prod) - ent4 = StubEntitlementCertificate(prod, pool=pool) - - inj.provide(inj.ENT_DIR, StubEntitlementDirectory([ent1, ent2, ent3, ent4])) - inj.provide(inj.PROD_DIR, StubProductDirectory([])) - cmd = managercli.RemoveCommand() - - cmd.main(["--serial=%s" % ent1.serial, "--serial=%s" % ent3.serial, "--pool=%s" % ent4.pool.id]) - self.assertTrue(cmd.entitlement_dir.list_called) - self.assertTrue(ent1.is_deleted) - self.assertFalse(ent2.is_deleted) - self.assertTrue(ent3.is_deleted) - self.assertTrue(ent4.is_deleted) diff --git a/test/test_repolib.py b/test/test_repolib.py index 102cfce0ac..9cc8ee481b 100644 --- a/test/test_repolib.py +++ b/test/test_repolib.py @@ -25,15 +25,17 @@ from iniparse import ConfigParser import rhsm.connection -from subscription_manager import repofile +from rhsm import repofile # repofile must be patched and reloaded to import AptRepofile, otherwise # the class is not defined in the first place +apt_mock = MagicMock() deb_mock = MagicMock() deb_mock.Deb822 = dict -with patch.dict("subscription_manager.repofile.sys.modules", {"debian.deb822": deb_mock}): - reload(repofile) - from subscription_manager.repofile import AptRepoFile +with patch.dict("rhsm.repofile.sys.modules", {"apt": apt_mock}): + with patch.dict("rhsm.repofile.sys.modules", {"debian.deb822": deb_mock}): + reload(repofile) + from rhsm.repofile import AptRepoFile reload(repofile) from .stubs import ( @@ -52,7 +54,7 @@ YumReleaseverSource, YumPluginManager, ) -from subscription_manager.repofile import Repo, TidyWriter, YumRepoFile +from rhsm.repofile import Repo, TidyWriter, YumRepoFile from subscription_manager import injection as inj from rhsm.config import RhsmConfigParser from rhsmlib.services import config @@ -907,14 +909,39 @@ def test_releasever_the_string_none(self): class AptRepoFileTest(unittest.TestCase): + keypath = "/etc/apt/trusted.gpg.d/" + + @classmethod + def setUpClass(cls) -> None: + + cls.original_os_listdir = os.listdir + + def mock_os_listdir(path): + """ + This method mock missing /etc/apt/trusted.gpg.d/ directory + :param path: of this directory + :return: Empty array if path is equal to keypath, otherwise return os.listdir(path) + """ + if path == cls.keypath: + return [] + else: + return cls.original_os_listdir(path) + + cls.patcher = patch("os.listdir", side_effect=mock_os_listdir) + cls.mock_exists = cls.patcher.start() + + @classmethod + def tearDownClass(cls) -> None: + cls.patcher.stop() + def _helper_stub_repo(self, *args, **kwargs): - with patch("subscription_manager.repofile.HAS_DEB822", True): + with patch("rhsm.repofile.HAS_DEB822", True): repo = Repo(*args, **kwargs) return repo def _helper_stub_repofile(self, *args, **kwargs): my_mock = MagicMock() - with patch("subscription_manager.repofile.RepoFileBase.create", my_mock): + with patch("rhsm.repofile.RepoFileBase.create", my_mock): repofile = AptRepoFile(*args, **kwargs) return repofile @@ -1092,8 +1119,8 @@ def test_fix_content_arches_multi(self, mock_file): class YumRepoFileTest(unittest.TestCase): - @patch("subscription_manager.repofile.YumRepoFile.create") - @patch("subscription_manager.repofile.TidyWriter") + @patch("rhsm.repofile.YumRepoFile.create") + @patch("rhsm.repofile.TidyWriter") def test_configparsers_equal(self, tidy_writer, stub_create): rf = YumRepoFile() other = RawConfigParser() @@ -1102,8 +1129,8 @@ def test_configparsers_equal(self, tidy_writer, stub_create): parser.set("test", "key", "val") self.assertTrue(rf._configparsers_equal(other)) - @patch("subscription_manager.repofile.YumRepoFile.create") - @patch("subscription_manager.repofile.TidyWriter") + @patch("rhsm.repofile.YumRepoFile.create") + @patch("rhsm.repofile.TidyWriter") def test_configparsers_diff_sections(self, tidy_writer, stub_create): rf = YumRepoFile() rf.add_section("new_section") @@ -1113,8 +1140,8 @@ def test_configparsers_diff_sections(self, tidy_writer, stub_create): parser.set("test", "key", "val") self.assertFalse(rf._configparsers_equal(other)) - @patch("subscription_manager.repofile.YumRepoFile.create") - @patch("subscription_manager.repofile.TidyWriter") + @patch("rhsm.repofile.YumRepoFile.create") + @patch("rhsm.repofile.TidyWriter") def test_configparsers_diff_item_val(self, tidy_writer, stub_create): rf = YumRepoFile() other = RawConfigParser() @@ -1124,8 +1151,8 @@ def test_configparsers_diff_item_val(self, tidy_writer, stub_create): rf.set("test", "key", "val2") self.assertFalse(rf._configparsers_equal(other)) - @patch("subscription_manager.repofile.YumRepoFile.create") - @patch("subscription_manager.repofile.TidyWriter") + @patch("rhsm.repofile.YumRepoFile.create") + @patch("rhsm.repofile.TidyWriter") def test_configparsers_diff_items(self, tidy_writer, stub_create): rf = YumRepoFile() other = RawConfigParser() @@ -1135,8 +1162,8 @@ def test_configparsers_diff_items(self, tidy_writer, stub_create): rf.set("test", "somekey", "val") self.assertFalse(rf._configparsers_equal(other)) - @patch("subscription_manager.repofile.YumRepoFile.create") - @patch("subscription_manager.repofile.TidyWriter") + @patch("rhsm.repofile.YumRepoFile.create") + @patch("rhsm.repofile.TidyWriter") def test_configparsers_equal_int(self, tidy_writer, stub_create): rf = YumRepoFile() other = RawConfigParser() diff --git a/test/test_syspurposestore_interface.py b/test/test_syspurposestore_interface.py deleted file mode 100644 index f733e34fe1..0000000000 --- a/test/test_syspurposestore_interface.py +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright (c) 2018 Red Hat, Inc. -# -# This software is licensed to you under the GNU General Public License, -# version 2 (GPLv2). There is NO WARRANTY for this software, express or -# implied, including the implied warranties of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -# along with this software; if not, see -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -# -# Red Hat trademarks are not licensed under GPLv2. No permission is -# granted to use or replicate Red Hat trademarks that are incorporated -# in this software or its documentation. -# - -import json -import shutil -import tempfile -import unittest -from .fixture import set_up_mock_sp_store - -import os -from unittest import mock - - -class SyspurposeStoreInterfaceTests(unittest.TestCase): - def setUp(self): - temp_dir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, temp_dir) - mock_syspurpose_file = os.path.join(temp_dir, "mock_syspurpose.json") - syspurpose_values = {} - with open(mock_syspurpose_file, "w") as f: - json.dump(syspurpose_values, f) - f.flush() - - from subscription_manager import syspurposelib - - self.syspurposelib = syspurposelib - syspurposelib.USER_SYSPURPOSE = mock_syspurpose_file - - syspurpose_patch = mock.patch("subscription_manager.syspurposelib.SyncedStore") - self.mock_sp_store = syspurpose_patch.start() - self.mock_sp_store, self.mock_sp_store_contents = set_up_mock_sp_store(self.mock_sp_store) - self.addCleanup(syspurpose_patch.stop) - - def _set_up_mock_sp_store(self): - """ - Sets up the mock syspurpose store with methods that are mock versions of the real deal. - Allows us to test in the absence of the syspurpose module. - :return: - """ - contents = {} - self.mock_sp_store_contents = contents - - def set(item, value): - contents[item] = value - - def read(path, raise_on_error=False): - return self.mock_sp_store - - def unset(item): - contents[item] = None - - def add(item, value): - current = contents.get(item, []) - if value not in current: - current.append(value) - contents[item] = current - - def remove(item, value): - current = contents.get(item) - if current is not None and isinstance(current, list) and value in current: - current.remove(value) - - self.mock_sp_store.set = mock.Mock(side_effect=set) - self.mock_sp_store.read = mock.Mock(side_effect=read) - self.mock_sp_store.unset = mock.Mock(side_effect=unset) - self.mock_sp_store.add = mock.Mock(side_effect=add) - self.mock_sp_store.remove = mock.Mock(side_effect=remove) - self.mock_sp_store.contents = self.mock_sp_store_contents - - def tearDown(self): - self.syspurposelib.USER_SYSPURPOSE = "/etc/rhsm/syspurpose/syspurpose.json" - - def test_save_sla_to_syspurpose_metadata_sla_is_set_when_syspurpose_module_exists(self): - """ - Tests that the syspurpose sla is set through the syspurposestore interface - when the syspurpose module is available for import. - """ - self.syspurposelib.save_sla_to_syspurpose_metadata(None, None, "Freemium") - - contents = self.syspurposelib.read_syspurpose() - self.assertEqual(contents.get("service_level_agreement"), "Freemium") - - def test_save_sla_to_syspurpose_metadata_sla_is_not_set_when_None_is_provided(self): - """ - Tests that the syspurpose sla is not set through the syspurposestore interface - when None is passed to save_sla_to_syspurpose_metadata method. - """ - self.syspurposelib.save_sla_to_syspurpose_metadata(None, None, None) - - contents = self.syspurposelib.read_syspurpose() - self.assertEqual(contents.get("service_level_agreement"), None) - - def test_save_sla_to_syspurpose_metadata_sla_is_not_set_when_empty_string_is_provided(self): - """ - Tests that the syspurpose sla is not set through the syspurposestore interface - when an empty string is passed to save_sla_to_syspurpose_metadata method. - """ - self.syspurposelib.save_sla_to_syspurpose_metadata(None, None, "") - - contents = self.syspurposelib.read_syspurpose() - self.assertEqual(contents.get("service_level_agreement"), None) - - def test_save_sla_to_syspurpose_metadata_sla_is_not_set_when_syspurpose_module_does_not_exist(self): - """ - Tests that the syspurpose sla is NOT set through the syspurposestore interface - when the syspurpose module is not available for import. - """ - # Remove SyspurposeStore and USER_SYSPURPOSE from syspurposestore_interface's scope temporarily - # to simulate that importing them failed. - tmp_syspurpose_store = self.syspurposelib.SyncedStore - tmp_user_syspurpose = self.syspurposelib.USER_SYSPURPOSE - del self.syspurposelib.SyncedStore - del self.syspurposelib.USER_SYSPURPOSE - - self.syspurposelib.save_sla_to_syspurpose_metadata(None, None, "Freemium") - - # Add SyspurposeStore and USER_SYSPURPOSE back to syspurposestore_interface's scope - self.syspurposelib.SyncedStore = tmp_syspurpose_store - self.syspurposelib.USER_SYSPURPOSE = tmp_user_syspurpose - - # Check that the contents of the syspurpose.json are empty (thus sla was not set) - contents = self.syspurposelib.read_syspurpose() - self.assertFalse(contents) diff --git a/test/test_utils.py b/test/test_utils.py index 3110a03709..57b7c69505 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -19,7 +19,6 @@ format_baseurl, get_version, get_client_versions, - unique_list_items, get_server_versions, friendly_join, is_true_value, @@ -585,22 +584,6 @@ def test_false_value(self): self.assertFalse(is_true_value("f")) -class TestUniqueListItems(fixture.SubManFixture): - def test_preserves_order(self): - input_list = [1, 1, 2, 2, 3, 3] - expected = [1, 2, 3] - self.assertEqual(expected, unique_list_items(input_list)) - - def test_hash_function(self): - mock_item_1 = Mock() - mock_item_1.value = 1 - mock_item_2 = Mock() - mock_item_2.value = 2 - input_list = [mock_item_1, mock_item_1, mock_item_2, mock_item_2] - expected = [mock_item_1, mock_item_2] - self.assertEqual(expected, unique_list_items(input_list, lambda x: x.value)) - - class TestProductCertificateFilter(fixture.SubManFixture): def test_set_filter_string(self): test_data = [ @@ -798,17 +781,12 @@ def setUp(self): super(TestIsOwnerUsingSimpleContentAccess, self).setUp() self.cp_provider = Mock() self.mock_uep = Mock() - self.mock_uep.getOwner = Mock(return_value=self.MOCK_ENTITLEMENT_OWNER) + self.mock_uep.getOwner = Mock(return_value=self.MOCK_ORG_ENVIRONMENT_OWNER) self.cp_provider.get_consumer_auth_cp = Mock(return_value=self.mock_uep) self.identity = Mock() self.identity.uuid = Mock(return_value="7f85da06-5c35-44ba-931d-f11f6e581f89") - def test_get_entitlement_owner(self): - ret = is_simple_content_access(uep=self.mock_uep, identity=self.identity) - self.assertFalse(ret) - def test_get_org_environment_owner(self): - self.mock_uep.getOwner = Mock(return_value=self.MOCK_ORG_ENVIRONMENT_OWNER) ret = is_simple_content_access(uep=self.mock_uep, identity=self.identity) self.assertTrue(ret) diff --git a/test/zypper/test_serviceplugin.py b/test/zypper/test_serviceplugin.py index 16472d525c..f4d858edcf 100644 --- a/test/zypper/test_serviceplugin.py +++ b/test/zypper/test_serviceplugin.py @@ -40,9 +40,6 @@ def test_provides_subman_repos_if_registered_and_subscribed(self): "--serverurl={RHSM_URL}".format(sub_man=self.SUB_MAN, **os.environ), shell=True, ) - subprocess.call( - "{sub_man} attach --pool={RHSM_POOL}".format(sub_man=self.SUB_MAN, **os.environ), shell=True - ) self.assertTrue(self.has_subman_repos()) def test_can_download_rpm(self): @@ -51,9 +48,6 @@ def test_can_download_rpm(self): "--serverurl={RHSM_URL}".format(sub_man=self.SUB_MAN, **os.environ), shell=True, ) - subprocess.check_call( - "{sub_man} attach --pool={RHSM_POOL}".format(sub_man=self.SUB_MAN, **os.environ), shell=True - ) subprocess.check_call( "{sub_man} repos --enable={RHSM_TEST_REPO}".format(sub_man=self.SUB_MAN, **os.environ), shell=True )