diff --git a/.github/workflows/auto_branching.yml b/.github/workflows/auto_branching.yml new file mode 100644 index 00000000000..6afb0cfa164 --- /dev/null +++ b/.github/workflows/auto_branching.yml @@ -0,0 +1,317 @@ +### The auto-branching workflow triggered through a dispatch request from the CI +name: auto-branching + +# Run on workflow dispatch from CI +on: + workflow_dispatch: + inputs: + target_branch: + type: string + description: branch to be created from the master + stream_version: + type: string + description: new stream version of satellite + +jobs: + check-group-membership: + runs-on: ubuntu-latest + outputs: + member: ${{steps.check_membership.outputs.member}} + + steps: + - name: Check if the user is a member of repository-admins group + id: check_membership + run: | + # Use GitHub API to check if the user triggering the workflow is a member of satellite-admin group + MEMBER=$(curl -s -H "Authorization: token ${{ secrets._REPO_ADMIN_TOKEN }}" \ + "https://api.github.com/orgs/satelliteQE/teams/repository-admins/memberships/${{ github.actor }}") + if [[ $(echo "$MEMBER" | jq -r '.state') == "active" ]]; then + echo "User is a member of satellite-admin group." + echo "member=true" >> $GITHUB_OUTPUT + else + echo "User is not a member of satellite-admin group." + echo "member=false" >> $GITHUB_OUTPUT + exit 1 + fi + + auto-branching-new-downstream-release: + name: ${{ github.event.inputs.target_branch }} - raise PR with changes + runs-on: ubuntu-latest + needs: check-group-membership + if: ${{ needs.check-group-membership.outputs.member == 'true' }} + + steps: + - uses: actions/checkout@v4 + + - name: Create the ${{ github.event.inputs.target_branch }} branch + id: create-branch + uses: peterjgrainger/action-create-branch@v3.0.0 + env: + GITHUB_TOKEN: ${{ secrets._REPO_ADMIN_TOKEN }} + with: + branch: ${{ github.event.inputs.target_branch }} + + - name: Create label for the ${{ github.event.inputs.target_branch }} branch + id: create-label + run: | + curl -X POST \ + -H "Authorization: token ${{ secrets._REPO_ADMIN_TOKEN }}" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/${{ github.repository }}/labels \ + -d "{\"name\":\"${{ github.event.inputs.target_branch }}\",\"color\":\"fbca04\"}" + + - name: Switch to ${{ github.event.inputs.target_branch }} branch + run: git checkout -b "${{ github.event.inputs.target_branch }}" + + - name: Checkout from ${{ github.event.inputs.target_branch }} branch for auto-branching changes + id: checkout-to-auto-branch + run: | + branch_name="auto-branching-${{ github.event.inputs.target_branch }}-$(date '+%s')" + git checkout -b "$branch_name" + echo "branch_name=$branch_name" >> $GITHUB_OUTPUT + + - name: Update target branch label in dependabot yml file + id: update-dependabot + run: | + # Read the dependabot.yml file + FILE_PATH="./.github/dependabot.yml" + TARGET_BRANCH="${{ github.event.inputs.target_branch }}" + # Append the target branch label to the labels node + awk -v target="'$TARGET_BRANCH'" '/^ *labels:/ {$0 = $0 "\n - " target} 1' "$FILE_PATH" > temp.yml && mv temp.yml "$FILE_PATH" + + - name: Update repository URLs in requirements.txt + id: update-repo-urls + run: | + # Define the file path + FILE_PATH="./requirements.txt" + # Define the replacement strings + replacements=( + "airgun @ git+https://github.com/SatelliteQE/airgun.git@master#egg=airgun|airgun @ git+https://github.com/SatelliteQE/airgun.git@${{ github.event.inputs.target_branch }}#egg=airgun" + "nailgun @ git+https://github.com/SatelliteQE/nailgun.git@master#egg=nailgun|nailgun @ git+https://github.com/SatelliteQE/nailgun.git@${{ github.event.inputs.target_branch }}#egg=nailgun" + ) + # Create a temporary file + TEMP_FILE=$(mktemp) + # Perform replacements using a for loop + for replacement in "${replacements[@]}"; do + old_url=$(echo "$replacement" | cut -d'|' -f1) + new_url=$(echo "$replacement" | cut -d'|' -f2) + sed "s|${old_url}|${new_url}|g" "$FILE_PATH" > "$TEMP_FILE" && mv "$TEMP_FILE" "$FILE_PATH" + done + + - name: Remove the dispatch release GHA + id: remove-dispatch-release-gha + run: | + rm -rf ./.github/workflows/dispatch_release.yml + rm -rf ./.github/workflows/auto_branching.yml + + - name: Remove lines with @pytest.mark.stream + id: remove-mark-stream + run: | + # Loop through files in the folder + grep -rl "tests/foreman" -e '@pytest\.mark\.stream' | while IFS= read -r file; do + awk '!/@pytest\.mark\.stream/' "$file" > temp && mv temp "$file" + done + + - name: Update version in setup.py + run: sed -i "s/version=['\"][0-9.]*['\"]\+/version='${{ github.event.inputs.target_branch }}'/" setup.py + + - name: Update the Constants in __init__.py file + run: | + old_url="https://raw.githubusercontent.com/SatelliteQE/robottelo/master/tests/foreman/data/uri.sh" + new_url="https://raw.githubusercontent.com/SatelliteQE/robottelo/${{ github.event.inputs.target_branch }}/tests/foreman/data/uri.sh" + FILE_PATH="./robottelo/constants/__init__.py" + awk '/SAT_NON_GA_VERSIONS =/ { sub(/\[[^,]*, /, "[", $0) } 1' "$FILE_PATH" > temp && mv temp "$FILE_PATH" + sed -i.bak "s|${old_url}|${new_url}|" "$FILE_PATH" + rm "$FILE_PATH.bak" + + - name: git status + run: git status + + - name: git diff + run: git diff + + - name: Commit changes + run: | + git config --local user.email Satellite-QE.satqe.com && git config --local user.name Satellite-QE + git add setup.py ./tests/foreman ./robottelo/* ./requirements.txt ./.github/* + git commit -m "Changes for ${{ github.event.inputs.target_branch }} new branch" + git push origin ${{steps.checkout-to-auto-branch.outputs.branch_name}} + + - name: Create pull request + id: create_pr + run: | + title="[${{ github.event.inputs.target_branch }}]: Changes for ${{ github.event.inputs.target_branch }} new branch" + body=" + ### Problem Statement + New ${{ github.event.inputs.target_branch }} branch + ### Solution + - Dependabot labels are updated for new branch + - Removed dispatch release GHA from ${{ github.event.inputs.target_branch }} as we are releasing only master changes + - Airgun and Nailgun Requirements uses ${{ github.event.inputs.target_branch }} branch + - Constants are using new version now + - Stream tests removed + - Setup.py uses new version + " + pr_number=$(gh pr create --title "$title" --body "$body" --base "${{ github.event.inputs.target_branch }}" | awk -F'/' '{print $NF}') + echo "$pr_number" + echo "pr_number=$pr_number" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets._REPO_ADMIN_TOKEN }} + + - name: Add the prt comment for running the sanity tests + id: add-parent-prt-comment + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + trigger: test-robottelo + pr_number: ${{ steps.create_pr.outputs.pr_number }} + GITHUB_TOKEN: ${{ secrets._REPO_ADMIN_TOKEN }} + + - name: add the no-cherrypick label + uses: actions/github-script@v7 + with: + github-token: ${{ secrets._REPO_ADMIN_TOKEN }} + script: | + github.rest.issues.addLabels({ + issue_number: ${{ steps.create_pr.outputs.pr_number }}, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ["No-CherryPick"] + }) + + branch-protection: + runs-on: ubuntu-latest + needs: auto-branching-new-downstream-release + if: success() + steps: + - name: Create branch protection + run: | + TOKEN=${{ secrets._REPO_ADMIN_TOKEN }} + OWNER=${{ github.repository_owner }} + REPO=${{ github.event.repository.name }} + BRANCH="${{ github.event.inputs.target_branch }}" # Adjust branch name as needed + # Branch protection payload + PROTECTION_PAYLOAD='{ + "required_status_checks": { + "strict": true, + "contexts": ["Code Quality (3.10)", "Code Quality (3.11)", "Code Quality (3.12)", "Enforcing cherrypick labels"] + }, + "required_linear_history": true, + "enforce_admins": null, + "required_pull_request_reviews": null, + "restrictions": null, + "allow_force_pushes": null, + "allow_deletions": null + }' + # Call GitHub API to update branch protection + PROTECTION_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \ + -X PUT \ + -H "Accept: application/vnd.github.luke-cage-preview+json" \ + -H "Authorization: token $TOKEN" \ + -d "$PROTECTION_PAYLOAD" \ + "https://api.github.com/repos/$OWNER/$REPO/branches/$BRANCH/protection") + + if [[ $PROTECTION_RESPONSE -eq 200 ]]; then + echo "Branch protection successfully updated." + echo "protection-outcome=success" >> "$GITHUB_OUTPUT" + else + echo "Failed to update branch protection. HTTP status code: $PROTECTION_RESPONSE" + echo "protection-outcome=failure" >> "$GITHUB_OUTPUT" + exit 1 + fi + + auto-branching-master: + name: master - raise PR with changes + runs-on: ubuntu-latest + needs: check-group-membership + if: ${{ needs.check-group-membership.outputs.member == 'true' }} + + steps: + - name: Checkout Robottelo + uses: actions/checkout@v4 + + - name: Update target branch label in dependabot yml file + id: update-dependabot + run: | + # Read the dependabot.yml file + FILE_PATH="./.github/dependabot.yml" + TARGET_BRANCH="${{ github.event.inputs.target_branch }}" + # Append the target branch label to the labels node + awk -v target="'$TARGET_BRANCH'" '/^ *labels:/ {$0 = $0 "\n - " target} 1' "$FILE_PATH" > temp.yml && mv temp.yml "$FILE_PATH" + + - name: Remove lines with @pytest.mark.stream + id: remove-mark-stream + run: | + # Loop through files in the folder + grep -rl "tests/foreman" -e '@pytest\.mark\.stream' | while IFS= read -r file; do + awk '!/@pytest\.mark\.stream/' "$file" > temp && mv temp "$file" + done + + - name: Update the Constants in __init__.py file + run: | + version="${{ github.event.inputs.target_branch }}" + ga_version="${{ github.event.inputs.ga_version }}" + old_stream_version="${version%.z}" + new_stream_version="${{ github.event.inputs.stream_version }}" + non_ga_versions="['$old_stream_version', '$new_stream_version']" + FILE_PATH="./robottelo/constants/__init__.py" + # update the version + sed -i.bak "s/SATELLITE_VERSION = \"$old_stream_version\"/SATELLITE_VERSION = \"$new_stream_version\"/" "$FILE_PATH" + sed -i.bak "s/ SATELLITE_VERSION: \"$old_stream_version\"/ SATELLITE_VERSION: \"$new_stream_version\"/" ./conf/robottelo.yaml.template + sed -i.bak "s/SAT_NON_GA_VERSIONS = \[.*\]/SAT_NON_GA_VERSIONS = $non_ga_versions/" "$FILE_PATH" + rm "$FILE_PATH.bak" "./conf/robottelo.yaml.template.bak" + + - name: git status + run: git status + + - name: git diff + run: git diff + + - name: Commit changes + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + branch_name="auto-branching-${{ github.event.inputs.target_branch }}-$(date '+%s')" + git checkout -b "$branch_name" + git add setup.py ./tests/foreman ./robottelo/* ./requirements.txt ./.github/* ./conf/robottelo.yaml.template + git commit -m "Changes for new ${{ github.event.inputs.target_branch }} branch" + git remote -vvv + git push origin "$branch_name" + + - name: Create pull request + id: create_pr + run: | + title="[master]: Changes for new ${{ github.event.inputs.target_branch }} branch" + body=" + ### Problem Statement + New ${{ github.event.inputs.target_branch }} downstream and master points to stream that is ${{ github.event.inputs.stream_version }} + ### Solution + - Dependabot.yaml cherrypicks to ${{ github.event.inputs.target_branch }} + - Robottelo conf and constants now uses ${{ github.event.inputs.stream_version }} and ${{ github.event.inputs.target_branch }} satellite versions + " + pr_number=$(gh pr create --title "$title" --body "$body" --base "master" | awk -F'/' '{print $NF}') + echo "$pr_number" + echo "pr_number=$pr_number" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets._REPO_ADMIN_TOKEN }} + + - name: Add the prt comment for running the sanity tests + id: add-parent-prt-comment + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + trigger: test-robottelo + pr_number: ${{ steps.create_pr.outputs.pr_number }} + GITHUB_TOKEN: ${{ secrets._REPO_ADMIN_TOKEN }} + + - name: add the no-cherrypick label + uses: actions/github-script@v7 + with: + github-token: ${{ secrets._REPO_ADMIN_TOKEN }} + script: | + github.rest.issues.addLabels({ + issue_number: ${{ steps.create_pr.outputs.pr_number }}, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ["No-CherryPick"] + }) diff --git a/.github/workflows/auto_cherry_pick_merge.yaml b/.github/workflows/auto_cherry_pick_merge.yaml index 5ee0c3b7bad..32bb94dec6c 100644 --- a/.github/workflows/auto_cherry_pick_merge.yaml +++ b/.github/workflows/auto_cherry_pick_merge.yaml @@ -40,7 +40,7 @@ jobs: - name: Wait for other status checks to Pass id: waitforstatuschecks - uses: lewagon/wait-on-check-action@v1.3.3 + uses: lewagon/wait-on-check-action@v1.3.4 with: ref: ${{ github.head_ref }} repo-token: ${{ secrets.CHERRYPICK_PAT }} @@ -67,7 +67,7 @@ jobs: - id: automerge name: Auto merge of cherry-picked PRs. - uses: "pascalgn/automerge-action@v0.16.2" + uses: "pascalgn/automerge-action@v0.16.3" env: GITHUB_TOKEN: "${{ secrets.CHERRYPICK_PAT }}" MERGE_LABELS: "AutoMerge_Cherry_Picked, Auto_Cherry_Picked" diff --git a/.github/workflows/dependency_merge.yml b/.github/workflows/dependency_merge.yml index f76d5a622dd..9db5b452fb9 100644 --- a/.github/workflows/dependency_merge.yml +++ b/.github/workflows/dependency_merge.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v1 + uses: dependabot/fetch-metadata@v2 with: github-token: "${{ secrets.GITHUB_TOKEN }}" @@ -51,7 +51,7 @@ jobs: - name: Wait for other status checks to Pass id: waitforstatuschecks - uses: lewagon/wait-on-check-action@v1.3.3 + uses: lewagon/wait-on-check-action@v1.3.4 with: ref: ${{ github.head_ref }} repo-token: ${{ secrets.CHERRYPICK_PAT }} @@ -61,7 +61,7 @@ jobs: - id: automerge name: Auto merge of dependabot PRs. - uses: "pascalgn/automerge-action@v0.16.2" + uses: "pascalgn/automerge-action@v0.16.3" env: GITHUB_TOKEN: "${{ secrets.CHERRYPICK_PAT }}" MERGE_LABELS: "dependencies" diff --git a/.github/workflows/prt_labels.yml b/.github/workflows/prt_labels.yml new file mode 100644 index 00000000000..072a46ba196 --- /dev/null +++ b/.github/workflows/prt_labels.yml @@ -0,0 +1,49 @@ +name: Remove the PRT label, for the new commit + +on: + pull_request_target: + types: ["synchronize"] + +jobs: + prt_labels_remover: + name: remove the PRT label when amendments or new commits added to PR + runs-on: ubuntu-latest + if: "(contains(github.event.pull_request.labels.*.name, 'PRT-Passed'))" + steps: + - name: Avoid the race condition as PRT result will be cleaned + run: | + echo "Avoiding the race condition if prt result will be cleaned" && sleep 60 + + - name: Fetch the PRT status + id: prt + uses: omkarkhatavkar/wait-for-status-checks@main + with: + ref: ${{ github.head_ref }} + context: 'Robottelo-Runner' + wait-interval: 2 + count: 5 + + - name: remove the PRT Passed label, for new commit + if: always() && ${{steps.prt.outputs.result}} == 'not_found' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.CHERRYPICK_PAT }} + script: | + const prNumber = '${{ github.event.number }}'; + const issue = await github.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + }); + const labelsToRemove = ['PRT-Passed']; + const labelsToRemoveFiltered = labelsToRemove.filter(label => issue.data.labels.some(({ name }) => name === label)); + if (labelsToRemoveFiltered.length > 0) { + await Promise.all(labelsToRemoveFiltered.map(async label => { + await github.rest.issues.removeLabel({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + name: label + }); + })); + } diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 04d41e73d36..382a20b0e48 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -8,6 +8,7 @@ on: env: PYCURL_SSL_LIBRARY: openssl ROBOTTELO_BUGZILLA__API_KEY: ${{ secrets.BUGZILLA_KEY }} + ROBOTTELO_JIRA__API_KEY: ${{ secrets.JIRA_KEY }} jobs: codechecks: @@ -41,9 +42,6 @@ jobs: cp broker_settings.yaml.example broker_settings.yaml cp .env.example .env - - name: Pre Commit Checks - uses: pre-commit/action@v3.0.1 - - name: Collect Tests run: | # To skip vault login in pull request checks diff --git a/.github/workflows/weekly.yml b/.github/workflows/weekly.yml index a08485c8f49..92fa9f329a9 100644 --- a/.github/workflows/weekly.yml +++ b/.github/workflows/weekly.yml @@ -55,6 +55,7 @@ jobs: id: cscheck env: ROBOTTELO_BUGZILLA__API_KEY: ${{ secrets.BUGZILLA_KEY }} + ROBOTTELO_JIRA__API_KEY: ${{ secrets.JIRA_KEY }} - name: Customer scenario status run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 08c10775ce1..c93c0b50203 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,8 @@ # configuration for pre-commit git hooks +ci: + autofix_prs: false # disable autofixing PRs + repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 @@ -13,7 +16,7 @@ repos: hooks: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.277 + rev: v0.3.0 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/conf/capsule.yaml.template b/conf/capsule.yaml.template index 9ef9e48a2c2..b618bf57ea9 100644 --- a/conf/capsule.yaml.template +++ b/conf/capsule.yaml.template @@ -1,4 +1,6 @@ CAPSULE: + # Capsule hostname for N-minus testing + HOSTNAME: VERSION: # The full release version (6.9.2) RELEASE: # populate with capsule version diff --git a/conf/dynaconf_hooks.py b/conf/dynaconf_hooks.py index 6d09d6e5bec..85baf7d52cb 100644 --- a/conf/dynaconf_hooks.py +++ b/conf/dynaconf_hooks.py @@ -85,9 +85,9 @@ def get_ohsnap_repos(settings): settings, repo='capsule', product='capsule', - release=settings.server.version.release, - os_release=settings.server.version.rhel_version, - snap=settings.server.version.snap, + release=settings.capsule.version.release, + os_release=settings.capsule.version.rhel_version, + snap=settings.capsule.version.snap, ) data['SATELLITE_REPO'] = get_ohsnap_repo_url( @@ -157,7 +157,7 @@ def get_dogfood_satclient_repos(settings): def get_ohsnap_repo_url(settings, repo, product=None, release=None, os_release=None, snap=''): - repourl = dogfood_repository( + return dogfood_repository( settings.ohsnap, repo=repo, product=product, @@ -165,4 +165,3 @@ def get_ohsnap_repo_url(settings, repo, product=None, release=None, os_release=N os_release=os_release, snap=snap, ).baseurl - return repourl diff --git a/conf/jira.yaml.template b/conf/jira.yaml.template new file mode 100644 index 00000000000..e76ac35f157 --- /dev/null +++ b/conf/jira.yaml.template @@ -0,0 +1,5 @@ +JIRA: + # url default value is set to 'https://issues.redhat.com' even if not provided. + URL: https://issues.redhat.com + # Provide api_key to access Jira REST API + API_KEY: replace-with-jira-api-key diff --git a/conf/oscap.yaml.template b/conf/oscap.yaml.template index bfeec7103fb..add2477848e 100644 --- a/conf/oscap.yaml.template +++ b/conf/oscap.yaml.template @@ -1,2 +1,6 @@ OSCAP: - CONTENT_PATH: /usr/share/xml/scap/ssg/content/ssg-rhel7-ds.xml + RHEL_MAJOR_VERSION: "@jinja {{this.server.version.rhel_version | int }}" + CONTENT_PATH: '@format /usr/share/xml/scap/ssg/content/ssg-rhel{this.oscap.rhel_major_version}-ds.xml' + # see: robottelo/constants/__init__.py OSCAP_PROFILE + PROFILE: '@format security{this.oscap.rhel_major_version}' + CONTENT_DIR: /usr/share/xml/scap/ssg/content diff --git a/conf/provisioning.yaml.template b/conf/provisioning.yaml.template index 308686cf84e..d2e547d91be 100644 --- a/conf/provisioning.yaml.template +++ b/conf/provisioning.yaml.template @@ -3,3 +3,5 @@ PROVISIONING: HOST_ROOT_PASSWORD: HOST_SSH_KEY_PRIV: HOST_SSH_KEY_PUB: + PROVISIONING_SAT_WORKFLOW: + PROVISIONING_HOST_WORKFLOW: diff --git a/conftest.py b/conftest.py index 42682d76249..7645728a06c 100644 --- a/conftest.py +++ b/conftest.py @@ -21,6 +21,7 @@ 'pytest_plugins.requirements.update_requirements', 'pytest_plugins.sanity_plugin', 'pytest_plugins.video_cleanup', + 'pytest_plugins.capsule_n-minus', # Fixtures 'pytest_fixtures.core.broker', 'pytest_fixtures.core.sat_cap_factory', diff --git a/pyproject.toml b/pyproject.toml index 4f5c8af613f..72a8e2b8f3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,8 @@ select = [ "I", # isort # "Q", # flake8-quotes "PT", # flake8-pytest + "RET", # flake8-return + "SIM", # flake8-simplify "UP", # pyupgrade "W", # pycodestyle ] diff --git a/pytest_fixtures/component/activationkey.py b/pytest_fixtures/component/activationkey.py index b0f7c77bd8a..8101a446ffa 100644 --- a/pytest_fixtures/component/activationkey.py +++ b/pytest_fixtures/component/activationkey.py @@ -8,41 +8,47 @@ @pytest.fixture(scope='module') def module_activation_key(module_sca_manifest_org, module_target_sat): """Create activation key using default CV and library environment.""" - activation_key = module_target_sat.api.ActivationKey( + return module_target_sat.api.ActivationKey( content_view=module_sca_manifest_org.default_content_view.id, environment=module_sca_manifest_org.library.id, organization=module_sca_manifest_org, ).create() - return activation_key + + +@pytest.fixture +def function_activation_key(function_sca_manifest_org, target_sat): + """Create activation key using default CV and library environment.""" + return target_sat.api.ActivationKey( + content_view=function_sca_manifest_org.default_content_view.id, + environment=function_sca_manifest_org.library.id, + organization=function_sca_manifest_org, + ).create() @pytest.fixture(scope='module') def module_ak(module_lce, module_org, module_target_sat): - ak = module_target_sat.api.ActivationKey( + return module_target_sat.api.ActivationKey( environment=module_lce, organization=module_org, ).create() - return ak @pytest.fixture(scope='module') def module_ak_with_cv(module_lce, module_org, module_promoted_cv, module_target_sat): - ak = module_target_sat.api.ActivationKey( + return module_target_sat.api.ActivationKey( content_view=module_promoted_cv, environment=module_lce, organization=module_org, ).create() - return ak @pytest.fixture(scope='module') def module_ak_with_cv_repo(module_lce, module_org, module_cv_repo, module_target_sat): - ak = module_target_sat.api.ActivationKey( + return module_target_sat.api.ActivationKey( content_view=module_cv_repo, environment=module_lce, organization=module_org, ).create() - return ak @pytest.fixture(scope='module') @@ -55,7 +61,7 @@ def module_ak_with_synced_repo(module_org, module_target_sat): {'product-id': new_product['id'], 'content-type': 'yum'} ) Repository.synchronize({'id': new_repo['id']}) - ak = module_target_sat.cli_factory.make_activation_key( + return module_target_sat.cli_factory.make_activation_key( { 'lifecycle-environment': 'Library', 'content-view': 'Default Organization View', @@ -63,4 +69,3 @@ def module_ak_with_synced_repo(module_org, module_target_sat): 'auto-attach': False, } ) - return ak diff --git a/pytest_fixtures/component/architecture.py b/pytest_fixtures/component/architecture.py index 96d758f3d64..5ff90f3cb37 100644 --- a/pytest_fixtures/component/architecture.py +++ b/pytest_fixtures/component/architecture.py @@ -6,22 +6,20 @@ @pytest.fixture(scope='session') def default_architecture(session_target_sat): - arch = ( + return ( session_target_sat.api.Architecture() .search(query={'search': f'name="{DEFAULT_ARCHITECTURE}"'})[0] .read() ) - return arch @pytest.fixture(scope='session') def session_puppet_default_architecture(session_puppet_enabled_sat): - arch = ( + return ( session_puppet_enabled_sat.api.Architecture() .search(query={'search': f'name="{DEFAULT_ARCHITECTURE}"'})[0] .read() ) - return arch @pytest.fixture(scope='module') diff --git a/pytest_fixtures/component/contentview.py b/pytest_fixtures/component/contentview.py index c1879f43338..79d66014949 100644 --- a/pytest_fixtures/component/contentview.py +++ b/pytest_fixtures/component/contentview.py @@ -36,12 +36,11 @@ def module_ak_cv_lce(module_org, module_lce, module_published_cv, module_target_ content_view_version = module_published_cv.version[0] content_view_version.promote(data={'environment_ids': module_lce.id}) module_published_cv = module_published_cv.read() - module_ak_with_cv_lce = module_target_sat.api.ActivationKey( + return module_target_sat.api.ActivationKey( content_view=module_published_cv, environment=module_lce, organization=module_org, ).create() - return module_ak_with_cv_lce @pytest.fixture(scope='module') diff --git a/pytest_fixtures/component/http_proxy.py b/pytest_fixtures/component/http_proxy.py index 8c6095092dd..a98e7409699 100644 --- a/pytest_fixtures/component/http_proxy.py +++ b/pytest_fixtures/component/http_proxy.py @@ -1,6 +1,13 @@ import pytest from robottelo.config import settings +from robottelo.hosts import ProxyHost + + +@pytest.fixture(scope='session') +def session_auth_proxy(session_target_sat): + """Instantiates authenticated HTTP proxy as a session-scoped fixture""" + return ProxyHost(settings.http_proxy.auth_proxy_url) @pytest.fixture diff --git a/pytest_fixtures/component/os.py b/pytest_fixtures/component/os.py index e6039c1b6f9..0c94baa09ce 100644 --- a/pytest_fixtures/component/os.py +++ b/pytest_fixtures/component/os.py @@ -26,8 +26,7 @@ def default_os( os.ptable.append(default_partitiontable) os.provisioning_template.append(default_pxetemplate) os.update(['architecture', 'ptable', 'provisioning_template']) - os = entities.OperatingSystem(id=os.id).read() - return os + return entities.OperatingSystem(id=os.id).read() @pytest.fixture(scope='module') diff --git a/pytest_fixtures/component/oscap.py b/pytest_fixtures/component/oscap.py index e8a7d230603..07dfd7a51e6 100644 --- a/pytest_fixtures/component/oscap.py +++ b/pytest_fixtures/component/oscap.py @@ -5,7 +5,7 @@ import pytest from robottelo.config import robottelo_tmp_dir, settings -from robottelo.constants import OSCAP_PROFILE, OSCAP_TAILORING_FILE, DataFile +from robottelo.constants import OSCAP_TAILORING_FILE, DataFile @pytest.fixture(scope="session") @@ -35,11 +35,10 @@ def scap_content(import_ansible_roles, module_target_sat): ) scap_id = scap_info['id'] scap_info = entities.ScapContents(id=scap_id).read() - scap_profile_id = [ profile['id'] for profile in scap_info.scap_content_profiles - if OSCAP_PROFILE['security7'] in profile['title'] + if module_target_sat.OSCAP['dsrhel'] in profile['title'] ][0] return { "title": title, diff --git a/pytest_fixtures/component/provision_azure.py b/pytest_fixtures/component/provision_azure.py index 27bcea16e1f..7e2a05f0707 100644 --- a/pytest_fixtures/component/provision_azure.py +++ b/pytest_fixtures/component/provision_azure.py @@ -44,17 +44,16 @@ def sat_azure_default_os(sat_azure): @pytest.fixture(scope='module') def sat_azure_default_architecture(sat_azure): - arch = ( + return ( sat_azure.api.Architecture() .search(query={'search': f'name="{DEFAULT_ARCHITECTURE}"'})[0] .read() ) - return arch @pytest.fixture(scope='session') def azurerm_settings(): - deps = { + return { 'tenant': settings.azurerm.tenant_id, 'app_ident': settings.azurerm.client_id, 'sub_id': settings.azurerm.subscription_id, @@ -62,7 +61,6 @@ def azurerm_settings(): 'secret': settings.azurerm.client_secret, 'region': settings.azurerm.azure_region.lower().replace(' ', ''), } - return deps @pytest.fixture(scope='session') @@ -86,7 +84,7 @@ def azurermclient(azurerm_settings): @pytest.fixture(scope='module') def module_azurerm_cr(azurerm_settings, sat_azure_org, sat_azure_loc, sat_azure): """Create AzureRM Compute Resource""" - azure_cr = sat_azure.api.AzureRMComputeResource( + return sat_azure.api.AzureRMComputeResource( name=gen_string('alpha'), provider='AzureRm', tenant=azurerm_settings['tenant'], @@ -97,7 +95,6 @@ def module_azurerm_cr(azurerm_settings, sat_azure_org, sat_azure_loc, sat_azure) organization=[sat_azure_org], location=[sat_azure_loc], ).create() - return azure_cr @pytest.fixture(scope='module') @@ -108,7 +105,7 @@ def module_azurerm_finishimg( module_azurerm_cr, ): """Creates Finish Template image on AzureRM Compute Resource""" - finish_image = sat_azure.api.Image( + return sat_azure.api.Image( architecture=sat_azure_default_architecture, compute_resource=module_azurerm_cr, name=gen_string('alpha'), @@ -116,7 +113,6 @@ def module_azurerm_finishimg( username=settings.azurerm.username, uuid=AZURERM_RHEL7_FT_IMG_URN, ).create() - return finish_image @pytest.fixture(scope='module') @@ -127,7 +123,7 @@ def module_azurerm_byos_finishimg( sat_azure, ): """Creates BYOS Finish Template image on AzureRM Compute Resource""" - finish_image = sat_azure.api.Image( + return sat_azure.api.Image( architecture=sat_azure_default_architecture, compute_resource=module_azurerm_cr, name=gen_string('alpha'), @@ -135,7 +131,6 @@ def module_azurerm_byos_finishimg( username=settings.azurerm.username, uuid=AZURERM_RHEL7_FT_BYOS_IMG_URN, ).create() - return finish_image @pytest.fixture(scope='module') @@ -146,7 +141,7 @@ def module_azurerm_cloudimg( module_azurerm_cr, ): """Creates cloudinit image on AzureRM Compute Resource""" - finish_image = sat_azure.api.Image( + return sat_azure.api.Image( architecture=sat_azure_default_architecture, compute_resource=module_azurerm_cr, name=gen_string('alpha'), @@ -155,7 +150,6 @@ def module_azurerm_cloudimg( uuid=AZURERM_RHEL7_UD_IMG_URN, user_data=True, ).create() - return finish_image @pytest.fixture(scope='module') @@ -166,7 +160,7 @@ def module_azurerm_gallery_finishimg( module_azurerm_cr, ): """Creates Shared Gallery Finish Template image on AzureRM Compute Resource""" - finish_image = sat_azure.api.Image( + return sat_azure.api.Image( architecture=sat_azure_default_architecture, compute_resource=module_azurerm_cr, name=gen_string('alpha'), @@ -174,7 +168,6 @@ def module_azurerm_gallery_finishimg( username=settings.azurerm.username, uuid=AZURERM_RHEL7_FT_GALLERY_IMG_URN, ).create() - return finish_image @pytest.fixture(scope='module') @@ -185,7 +178,7 @@ def module_azurerm_custom_finishimg( module_azurerm_cr, ): """Creates Custom Finish Template image on AzureRM Compute Resource""" - finish_image = sat_azure.api.Image( + return sat_azure.api.Image( architecture=sat_azure_default_architecture, compute_resource=module_azurerm_cr, name=gen_string('alpha'), @@ -193,4 +186,3 @@ def module_azurerm_custom_finishimg( username=settings.azurerm.username, uuid=AZURERM_RHEL7_FT_CUSTOM_IMG_URN, ).create() - return finish_image diff --git a/pytest_fixtures/component/provision_capsule_pxe.py b/pytest_fixtures/component/provision_capsule_pxe.py index 04a2c4fb475..dc8dd48471f 100644 --- a/pytest_fixtures/component/provision_capsule_pxe.py +++ b/pytest_fixtures/component/provision_capsule_pxe.py @@ -35,7 +35,7 @@ def capsule_provisioning_sat( sat = module_target_sat provisioning_domain_name = f"{gen_string('alpha').lower()}.foo" broker_data_out = Broker().execute( - workflow='configure-install-sat-provisioning-rhv', + workflow=settings.provisioning.provisioning_sat_workflow, artifacts='last', target_vlan_id=settings.provisioning.vlan_id, target_host=module_capsule_configured.name, @@ -209,7 +209,7 @@ def capsule_provisioning_rhel_content( releasever=constants.REPOS['kickstart'][name]['version'], ) # do not sync content repos for discovery based provisioning. - if not capsule_provisioning_sat.provisioning_type == 'discovery': + if capsule_provisioning_sat.provisioning_type != 'discovery': rh_repo_id = sat.api_factory.enable_rhrepo_and_fetchid( basearch=constants.DEFAULT_ARCHITECTURE, org_id=module_sca_manifest_org.id, diff --git a/pytest_fixtures/component/provision_gce.py b/pytest_fixtures/component/provision_gce.py index 4c11ecb0918..64b706d772e 100644 --- a/pytest_fixtures/component/provision_gce.py +++ b/pytest_fixtures/component/provision_gce.py @@ -53,12 +53,11 @@ def sat_gce_default_partition_table(sat_gce): @pytest.fixture(scope='module') def sat_gce_default_architecture(sat_gce): - arch = ( + return ( sat_gce.api.Architecture() .search(query={'search': f'name="{DEFAULT_ARCHITECTURE}"'})[0] .read() ) - return arch @pytest.fixture(scope='session') @@ -96,8 +95,7 @@ def gce_latest_rhel_uuid(googleclient): filter_expr=f'name:{GCE_TARGET_RHEL_IMAGE_NAME}*', ) latest_template_name = max(tpl.name for tpl in templates) - latest_template_uuid = next(tpl for tpl in templates if tpl.name == latest_template_name).uuid - return latest_template_uuid + return next(tpl for tpl in templates if tpl.name == latest_template_name).uuid @pytest.fixture(scope='session') @@ -116,7 +114,7 @@ def session_default_os(session_target_sat): @pytest.fixture(scope='module') def module_gce_compute(sat_gce, sat_gce_org, sat_gce_loc, gce_cert): - gce_cr = sat_gce.api.GCEComputeResource( + return sat_gce.api.GCEComputeResource( name=gen_string('alphanumeric'), provider='GCE', key_path=settings.gce.cert_path, @@ -124,7 +122,6 @@ def module_gce_compute(sat_gce, sat_gce_org, sat_gce_loc, gce_cert): organization=[sat_gce_org], location=[sat_gce_loc], ).create() - return gce_cr @pytest.fixture(scope='module') @@ -206,7 +203,7 @@ def gce_hostgroup( googleclient, ): """Sets Hostgroup for GCE Host Provisioning""" - hgroup = sat_gce.api.HostGroup( + return sat_gce.api.HostGroup( architecture=sat_gce_default_architecture, compute_resource=module_gce_compute, domain=sat_gce_domain, @@ -216,7 +213,6 @@ def gce_hostgroup( organization=[sat_gce_org], ptable=sat_gce_default_partition_table, ).create() - return hgroup @pytest.fixture(scope='module') @@ -250,7 +246,7 @@ def module_gce_cloudimg( sat_gce, ): """Creates cloudinit image on GCE Compute Resource""" - cloud_image = sat_gce.api.Image( + return sat_gce.api.Image( architecture=sat_gce_default_architecture, compute_resource=module_gce_compute, name=gen_string('alpha'), @@ -259,7 +255,6 @@ def module_gce_cloudimg( uuid=gce_custom_cloudinit_uuid, user_data=True, ).create() - return cloud_image @pytest.fixture(scope='module') @@ -271,7 +266,7 @@ def module_gce_finishimg( sat_gce, ): """Creates finish image on GCE Compute Resource""" - finish_image = sat_gce.api.Image( + return sat_gce.api.Image( architecture=sat_gce_default_architecture, compute_resource=module_gce_compute, name=gen_string('alpha'), @@ -279,7 +274,6 @@ def module_gce_finishimg( username=gen_string('alpha'), uuid=gce_latest_rhel_uuid, ).create() - return finish_image @pytest.fixture diff --git a/pytest_fixtures/component/provision_pxe.py b/pytest_fixtures/component/provision_pxe.py index 2fc375bc5f1..18c98242fc7 100644 --- a/pytest_fixtures/component/provision_pxe.py +++ b/pytest_fixtures/component/provision_pxe.py @@ -70,7 +70,7 @@ def module_provisioning_rhel_content( releasever=constants.REPOS['kickstart'][name]['version'], ) # do not sync content repos for discovery based provisioning. - if not module_provisioning_sat.provisioning_type == 'discovery': + if module_provisioning_sat.provisioning_type != 'discovery': rh_repo_id = sat.api_factory.enable_rhrepo_and_fetchid( basearch=constants.DEFAULT_ARCHITECTURE, org_id=module_sca_manifest_org.id, @@ -152,7 +152,7 @@ def module_provisioning_sat( provisioning_domain_name = f"{gen_string('alpha').lower()}.foo" broker_data_out = Broker().execute( - workflow='configure-install-sat-provisioning-rhv', + workflow=settings.provisioning.provisioning_sat_workflow, artifacts='last', target_vlan_id=settings.provisioning.vlan_id, target_host=sat.name, @@ -223,7 +223,7 @@ def provisioning_host(module_ssh_key_file, pxe_loader): "" # TODO: Make this an optional fixture parameter (update vm_firmware when adding this) ) with Broker( - workflow="deploy-configure-pxe-provisioning-host-rhv", + workflow=settings.provisioning.provisioning_host_workflow, host_class=ContentHost, target_vlan_id=vlan_id, target_vm_firmware=pxe_loader.vm_firmware, @@ -245,7 +245,7 @@ def provision_multiple_hosts(module_ssh_key_file, pxe_loader, request): "" # TODO: Make this an optional fixture parameter (update vm_firmware when adding this) ) with Broker( - workflow="deploy-configure-pxe-provisioning-host-rhv", + workflow=settings.provisioning.provisioning_host_workflow, host_class=ContentHost, _count=getattr(request, 'param', 2), target_vlan_id=vlan_id, diff --git a/pytest_fixtures/component/provision_vmware.py b/pytest_fixtures/component/provision_vmware.py index 507f180a158..0fb9f84ff38 100644 --- a/pytest_fixtures/component/provision_vmware.py +++ b/pytest_fixtures/component/provision_vmware.py @@ -1,5 +1,6 @@ from fauxfactory import gen_string import pytest +from wrapanapi import VMWareSystem from robottelo.config import settings @@ -13,9 +14,20 @@ def vmware(request): return versions[getattr(request, 'param', 'vmware8')] +@pytest.fixture +def vmwareclient(vmware): + vmwareclient = VMWareSystem( + hostname=vmware.hostname, + username=settings.vmware.username, + password=settings.vmware.password, + ) + yield vmwareclient + vmwareclient.disconnect() + + @pytest.fixture(scope='module') def module_vmware_cr(module_provisioning_sat, module_sca_manifest_org, module_location, vmware): - vmware_cr = module_provisioning_sat.sat.api.VMWareComputeResource( + return module_provisioning_sat.sat.api.VMWareComputeResource( name=gen_string('alpha'), provider='Vmware', url=vmware.hostname, @@ -25,7 +37,6 @@ def module_vmware_cr(module_provisioning_sat, module_sca_manifest_org, module_lo organization=[module_sca_manifest_org], location=[module_location], ).create() - return vmware_cr @pytest.fixture diff --git a/pytest_fixtures/component/provisioning_template.py b/pytest_fixtures/component/provisioning_template.py index 97439a8a07a..294edcbb22e 100644 --- a/pytest_fixtures/component/provisioning_template.py +++ b/pytest_fixtures/component/provisioning_template.py @@ -17,8 +17,7 @@ def module_provisioningtemplate_default(module_org, module_location): provisioning_template.organization.append(module_org) provisioning_template.location.append(module_location) provisioning_template.update(['organization', 'location']) - provisioning_template = entities.ProvisioningTemplate(id=provisioning_template.id).read() - return provisioning_template + return entities.ProvisioningTemplate(id=provisioning_template.id).read() @pytest.fixture(scope='module') @@ -30,8 +29,7 @@ def module_provisioningtemplate_pxe(module_org, module_location): pxe_template.organization.append(module_org) pxe_template.location.append(module_location) pxe_template.update(['organization', 'location']) - pxe_template = entities.ProvisioningTemplate(id=pxe_template.id).read() - return pxe_template + return entities.ProvisioningTemplate(id=pxe_template.id).read() @pytest.fixture(scope='session') @@ -39,6 +37,7 @@ def default_partitiontable(): ptables = entities.PartitionTable().search(query={'search': f'name="{DEFAULT_PTABLE}"'}) if ptables: return ptables[0].read() + return None @pytest.fixture(scope='module') diff --git a/pytest_fixtures/component/puppet.py b/pytest_fixtures/component/puppet.py index 38cad7163c0..c531c7888ac 100644 --- a/pytest_fixtures/component/puppet.py +++ b/pytest_fixtures/component/puppet.py @@ -44,6 +44,7 @@ def default_puppet_environment(module_puppet_org, session_puppet_enabled_sat): ) if environments: return environments[0].read() + return None @pytest.fixture(scope='module') @@ -101,10 +102,7 @@ def module_puppet_classes( @pytest.fixture(scope='session', params=[True, False], ids=["puppet_enabled", "puppet_disabled"]) def parametrized_puppet_sat(request, session_target_sat, session_puppet_enabled_sat): - if request.param: - sat = session_puppet_enabled_sat - else: - sat = session_target_sat + sat = session_puppet_enabled_sat if request.param else session_target_sat return {'sat': sat, 'enabled': request.param} diff --git a/pytest_fixtures/component/repository.py b/pytest_fixtures/component/repository.py index ba92e497446..71848e3e7e7 100644 --- a/pytest_fixtures/component/repository.py +++ b/pytest_fixtures/component/repository.py @@ -68,8 +68,7 @@ def repo_setup(): product = entities.Product(organization=org).create() repo = entities.Repository(name=repo_name, product=product).create() lce = entities.LifecycleEnvironment(organization=org).create() - details = {'org': org, 'product': product, 'repo': repo, 'lce': lce} - return details + return {'org': org, 'product': product, 'repo': repo, 'lce': lce} @pytest.fixture(scope='module') @@ -190,7 +189,7 @@ def repos_collection(request, target_sat): """ repos = getattr(request, 'param', []) repo_distro, repos = _simplify_repos(request, repos) - _repos_collection = target_sat.cli_factory.RepositoryCollection( + return target_sat.cli_factory.RepositoryCollection( distro=repo_distro or request.getfixturevalue('distro'), repositories=[ getattr(target_sat.cli_factory, repo_name)(**repo_params) @@ -198,7 +197,6 @@ def repos_collection(request, target_sat): for repo_name, repo_params in repo.items() ], ) - return _repos_collection @pytest.fixture(scope='module') diff --git a/pytest_fixtures/component/rh_cloud.py b/pytest_fixtures/component/rh_cloud.py index ead92f68db2..fd035f653c3 100644 --- a/pytest_fixtures/component/rh_cloud.py +++ b/pytest_fixtures/component/rh_cloud.py @@ -15,7 +15,7 @@ def rhcloud_manifest_org(module_target_sat, module_extra_rhel_entitlement_manife def rhcloud_activation_key(module_target_sat, rhcloud_manifest_org): """A module-level fixture to create an Activation key in module_org""" purpose_addons = "test-addon1, test-addon2" - ak = module_target_sat.api.ActivationKey( + return module_target_sat.api.ActivationKey( content_view=rhcloud_manifest_org.default_content_view, organization=rhcloud_manifest_org, environment=module_target_sat.api.LifecycleEnvironment(id=rhcloud_manifest_org.library.id), @@ -25,7 +25,6 @@ def rhcloud_activation_key(module_target_sat, rhcloud_manifest_org): purpose_role='test-role', auto_attach=False, ).create() - return ak @pytest.fixture(scope='module') diff --git a/pytest_fixtures/component/satellite_auth.py b/pytest_fixtures/component/satellite_auth.py index 4eb984390f6..9af5a98287a 100644 --- a/pytest_fixtures/component/satellite_auth.py +++ b/pytest_fixtures/component/satellite_auth.py @@ -271,11 +271,12 @@ def auth_data(request, ad_data, ipa_data): ad_data['attr_login'] = LDAP_ATTR['login_ad'] ad_data['auth_type'] = auth_type return ad_data - elif auth_type == 'ipa': + if auth_type == 'ipa': ipa_data['server_type'] = LDAP_SERVER_TYPE['UI']['ipa'] ipa_data['attr_login'] = LDAP_ATTR['login'] ipa_data['auth_type'] = auth_type return ipa_data + return None @pytest.fixture(scope='module') @@ -455,10 +456,10 @@ def configure_hammer_no_negotiate(parametrized_enrolled_sat): def hammer_logout(parametrized_enrolled_sat): """Logout in Hammer.""" result = parametrized_enrolled_sat.cli.Auth.logout() - assert result[0]['message'] == LOGGEDOUT + assert result.split("\n")[1] == LOGGEDOUT yield result = parametrized_enrolled_sat.cli.Auth.logout() - assert result[0]['message'] == LOGGEDOUT + assert result.split("\n")[1] == LOGGEDOUT @pytest.fixture diff --git a/pytest_fixtures/component/taxonomy.py b/pytest_fixtures/component/taxonomy.py index e6ac87357cd..c7f53011a84 100644 --- a/pytest_fixtures/component/taxonomy.py +++ b/pytest_fixtures/component/taxonomy.py @@ -23,14 +23,14 @@ def default_location(session_target_sat): def current_sat_org(target_sat): """Return the current organization assigned to the Satellite host""" sat_host = target_sat.api.Host().search(query={'search': f'name={target_sat.hostname}'})[0] - return sat_host.organization.read().name + return sat_host.organization.read() @pytest.fixture def current_sat_location(target_sat): """Return the current location assigned to the Satellite host""" sat_host = target_sat.api.Host().search(query={'search': f'name={target_sat.hostname}'})[0] - return sat_host.location.read().name + return sat_host.location.read() @pytest.fixture diff --git a/pytest_fixtures/component/user.py b/pytest_fixtures/component/user.py index 3f4d1034e6e..a1b9b7a1a9f 100644 --- a/pytest_fixtures/component/user.py +++ b/pytest_fixtures/component/user.py @@ -9,5 +9,4 @@ def user_not_exists(request): if users: users[0].delete() return True - else: - return False + return False diff --git a/pytest_fixtures/core/broker.py b/pytest_fixtures/core/broker.py index 9d207fcd9d7..f3356dbbef5 100644 --- a/pytest_fixtures/core/broker.py +++ b/pytest_fixtures/core/broker.py @@ -16,6 +16,7 @@ def _default_sat(align_to_satellite): return Satellite.get_host_by_hostname(settings.server.hostname) except ContentHostError: return Satellite() + return None @contextmanager diff --git a/pytest_fixtures/core/contenthosts.py b/pytest_fixtures/core/contenthosts.py index 485591ed7d9..d2bbb4cce39 100644 --- a/pytest_fixtures/core/contenthosts.py +++ b/pytest_fixtures/core/contenthosts.py @@ -45,6 +45,15 @@ def rhel_contenthost(request): yield host +@pytest.fixture(scope='module') +def module_rhel_contenthost(request): + """A module-level fixture that provides a content host object parametrized""" + # Request should be parametrized through pytest_fixtures.fixture_markers + # unpack params dict + with Broker(**host_conf(request), host_class=ContentHost) as host: + yield host + + @pytest.fixture(params=[{'rhel_version': '7'}]) def rhel7_contenthost(request): """A function-level fixture that provides a rhel7 content host object""" @@ -161,6 +170,16 @@ def rex_contenthost(request, module_org, target_sat, module_ak_with_cv): yield host +@pytest.fixture +def rex_contenthosts(request, module_org, target_sat, module_ak_with_cv): + request.param['no_containers'] = True + with Broker(**host_conf(request), host_class=ContentHost, _count=2) as hosts: + for host in hosts: + repo = settings.repos['SATCLIENT_REPO'][f'RHEL{host.os_version.major}'] + host.register(module_org, None, module_ak_with_cv.name, target_sat, repo=repo) + yield hosts + + @pytest.fixture def katello_host_tools_tracer_host(rex_contenthost, target_sat): """Install katello-host-tools-tracer, create custom @@ -268,8 +287,10 @@ def sat_upgrade_chost(): def custom_host(request): """A rhel content host that passes custom host config through request.param""" deploy_args = request.param - # if 'deploy_rhel_version' is not set, let's default to RHEL 8 - deploy_args['deploy_rhel_version'] = deploy_args.get('deploy_rhel_version', '8') + # if 'deploy_rhel_version' is not set, let's default to what's in content_host.yaml + deploy_args['deploy_rhel_version'] = deploy_args.get( + 'deploy_rhel_version', settings.content_host.default_rhel_version + ) deploy_args['workflow'] = 'deploy-rhel' with Broker(**deploy_args, host_class=Satellite) as host: yield host diff --git a/pytest_fixtures/core/reporting.py b/pytest_fixtures/core/reporting.py index baf9d3cde7d..c89c87d46da 100644 --- a/pytest_fixtures/core/reporting.py +++ b/pytest_fixtures/core/reporting.py @@ -35,13 +35,14 @@ def pytest_sessionstart(session): remove if resolved and set autouse=True for record_testsuite_timestamp_xml fixture """ - if get_xdist_worker_id(session) == 'master': - if session.config.pluginmanager.hasplugin('junitxml'): - xml = session.config._store.get(xml_key, None) - if xml: - xml.add_global_property( - 'start_time', datetime.datetime.utcnow().strftime(FMT_XUNIT_TIME) - ) + if get_xdist_worker_id(session) == 'master' and session.config.pluginmanager.hasplugin( + 'junitxml' + ): + xml = session.config._store.get(xml_key, None) + if xml: + xml.add_global_property( + 'start_time', datetime.datetime.utcnow().strftime(FMT_XUNIT_TIME) + ) @pytest.fixture(autouse=False, scope='session') diff --git a/pytest_fixtures/core/sat_cap_factory.py b/pytest_fixtures/core/sat_cap_factory.py index c63a70e4d19..9634c6c4fcb 100644 --- a/pytest_fixtures/core/sat_cap_factory.py +++ b/pytest_fixtures/core/sat_cap_factory.py @@ -1,4 +1,5 @@ from contextlib import contextmanager +from functools import lru_cache from broker import Broker from packaging.version import Version @@ -37,13 +38,29 @@ def _target_satellite_host(request, satellite_factory): yield +@lru_cache +def cached_capsule_cdn_register(hostname=None): + cap = Capsule.get_host_by_hostname(hostname=hostname) + cap.enable_capsule_downstream_repos() + + @contextmanager def _target_capsule_host(request, capsule_factory): - if 'sanity' not in request.config.option.markexpr: + if 'sanity' not in request.config.option.markexpr and not request.config.option.n_minus: new_cap = capsule_factory() yield new_cap new_cap.teardown() Broker(hosts=[new_cap]).checkin() + elif request.config.option.n_minus: + if not settings.capsule.hostname: + hosts = Capsule.get_hosts_from_inventory(filter="'cap' in @inv.name") + settings.capsule.hostname = hosts[0].hostname + cap = hosts[0] + else: + cap = Capsule.get_host_by_hostname(settings.capsule.hostname) + # Capsule needs RHEL contents for some tests + cached_capsule_cdn_register(hostname=settings.capsule.hostname) + yield cap else: yield @@ -162,9 +179,10 @@ def session_capsule_host(request, capsule_factory): @pytest.fixture -def capsule_configured(capsule_host, target_sat): +def capsule_configured(request, capsule_host, target_sat): """Configure the capsule instance with the satellite from settings.server.hostname""" - capsule_host.capsule_setup(sat_host=target_sat) + if not request.config.option.n_minus: + capsule_host.capsule_setup(sat_host=target_sat) return capsule_host @@ -176,21 +194,23 @@ def large_capsule_configured(large_capsule_host, target_sat): @pytest.fixture(scope='module') -def module_capsule_configured(module_capsule_host, module_target_sat): +def module_capsule_configured(request, module_capsule_host, module_target_sat): """Configure the capsule instance with the satellite from settings.server.hostname""" - module_capsule_host.capsule_setup(sat_host=module_target_sat) + if not request.config.option.n_minus: + module_capsule_host.capsule_setup(sat_host=module_target_sat) return module_capsule_host @pytest.fixture(scope='session') -def session_capsule_configured(session_capsule_host, session_target_sat): +def session_capsule_configured(request, session_capsule_host, session_target_sat): """Configure the capsule instance with the satellite from settings.server.hostname""" - session_capsule_host.capsule_setup(sat_host=session_target_sat) + if not request.config.option.n_minus: + session_capsule_host.capsule_setup(sat_host=session_target_sat) return session_capsule_host @pytest.fixture(scope='module') -def module_capsule_configured_mqtt(module_capsule_configured): +def module_capsule_configured_mqtt(request, module_capsule_configured): """Configure the capsule instance with the satellite from settings.server.hostname, enable MQTT broker""" module_capsule_configured.set_rex_script_mode_provider('pull-mqtt') @@ -201,7 +221,9 @@ def module_capsule_configured_mqtt(module_capsule_configured): result = module_capsule_configured.execute('firewall-cmd --permanent --add-port="1883/tcp"') assert result.status == 0, 'Failed to open mqtt port on capsule' module_capsule_configured.execute('firewall-cmd --reload') - return module_capsule_configured + yield module_capsule_configured + if request.config.option.n_minus: + raise TypeError('The teardown is missed for MQTT configuration undo for nminus testing') @pytest.fixture(scope='module') @@ -228,6 +250,18 @@ def module_lb_capsule(retry_limit=3, delay=300, **broker_args): Broker(hosts=cap_hosts.out).checkin() +@pytest.fixture(scope='module') +def module_capsule_configured_ansible(module_capsule_configured): + """Configure the capsule instance with Ansible feature enabled""" + result = module_capsule_configured.install( + cmd_args=[ + 'enable-foreman-proxy-plugin-ansible', + ] + ) + assert result.status == 0, 'Installer failed to enable ansible plugin.' + return module_capsule_configured + + @pytest.fixture(scope='module') def module_capsule_configured_async_ssh(module_capsule_configured): """Configure the capsule instance with the satellite from settings.server.hostname, @@ -324,7 +358,7 @@ def installer_satellite(request): release=settings.server.version.release, snap=settings.server.version.snap, ) - sat.execute('dnf -y module enable satellite:el8 && dnf -y install satellite') + sat.install_satellite_or_capsule_package() installed_version = sat.execute('rpm --query satellite').stdout assert sat_version in installed_version # Install Satellite diff --git a/pytest_fixtures/core/ui.py b/pytest_fixtures/core/ui.py index c266e363c7e..1c24d6f04a9 100644 --- a/pytest_fixtures/core/ui.py +++ b/pytest_fixtures/core/ui.py @@ -102,7 +102,8 @@ def ui_session_record_property(request, record_property): test_file_path = request.node.fspath.strpath if any(directory in test_file_path for directory in test_directories): for fixture in request.node.fixturenames: - if request.fixturename != fixture: - if isinstance(request.getfixturevalue(fixture), Satellite): - sat = request.getfixturevalue(fixture) - sat.record_property = record_property + if request.fixturename != fixture and isinstance( + request.getfixturevalue(fixture), Satellite + ): + sat = request.getfixturevalue(fixture) + sat.record_property = record_property diff --git a/pytest_fixtures/core/upgrade.py b/pytest_fixtures/core/upgrade.py index caf4532713b..5726f9f80a9 100644 --- a/pytest_fixtures/core/upgrade.py +++ b/pytest_fixtures/core/upgrade.py @@ -10,12 +10,11 @@ def dependent_scenario_name(request): """ This fixture is used to collect the dependent test case name. """ - depend_test_name = [ + return [ mark.kwargs['depend_on'].__name__ for mark in request.node.own_markers if 'depend_on' in mark.kwargs ][0] - return depend_test_name @pytest.fixture(scope="session") diff --git a/pytest_fixtures/core/xdist.py b/pytest_fixtures/core/xdist.py index 4d02fe026d0..2495e4fa1c2 100644 --- a/pytest_fixtures/core/xdist.py +++ b/pytest_fixtures/core/xdist.py @@ -1,4 +1,5 @@ """Fixtures specific to or relating to pytest's xdist plugin""" + import random from broker import Broker @@ -31,9 +32,17 @@ def align_to_satellite(request, worker_id, satellite_factory): # attempt to add potential satellites from the broker inventory file if settings.server.inventory_filter: + logger.info( + f'{worker_id=}: Attempting to add Satellite hosts using inventory filter: ' + f'{settings.server.inventory_filter}' + ) hosts = Satellite.get_hosts_from_inventory(filter=settings.server.inventory_filter) settings.server.hostnames += [host.hostname for host in hosts] + logger.debug( + f'{worker_id=}: {settings.server.xdist_behavior=}, ' + f'{settings.server.hostnames=}, {settings.server.auto_checkin=}' + ) # attempt to align a worker to a satellite if settings.server.xdist_behavior == 'run-on-one' and settings.server.hostnames: settings.set("server.hostname", settings.server.hostnames[0]) @@ -48,14 +57,19 @@ def align_to_satellite(request, worker_id, satellite_factory): settings.set("server.hostname", on_demand_sat.hostname) # if no satellite was received, fallback to balance if not settings.server.hostname: + logger.info( + f'{worker_id=}: No Satellite hostnames were available, ' + 'falling back to balance behavior' + ) settings.set("server.hostname", random.choice(settings.server.hostnames)) if settings.server.hostname: - logger.info( - f'xdist worker {worker_id} was assigned hostname {settings.server.hostname}' - ) + logger.info(f'{worker_id=}: Worker was assigned hostname {settings.server.hostname}') configure_airgun() configure_nailgun() yield if on_demand_sat and settings.server.auto_checkin: + logger.info( + f'{worker_id=}: Checking in on-demand Satellite ' f'{on_demand_sat.hostname}' + ) on_demand_sat.teardown() Broker(hosts=[on_demand_sat]).checkin() diff --git a/pytest_plugins/capsule_n-minus.py b/pytest_plugins/capsule_n-minus.py new file mode 100644 index 00000000000..f903e239757 --- /dev/null +++ b/pytest_plugins/capsule_n-minus.py @@ -0,0 +1,55 @@ +# Collection of Capsule Factory fixture tests +# No destructive tests +# Adjust capsule host and capsule_configured host behavior for n_minus testing +# Calculate capsule hostname from inventory just as we do in xDist.py +from robottelo.config import settings +from robottelo.hosts import Capsule + + +def pytest_addoption(parser): + """Add options for pytest to collect tests based on fixtures its using""" + help_text = ''' + Collects tests based on capsule fixtures used by tests and uncollect destructive tests + + Usage: --n-minus + + example: pytest --n-minus tests/foreman + ''' + parser.addoption("--n-minus", action='store_true', default=False, help=help_text) + + +def pytest_collection_modifyitems(items, config): + + if not config.getoption('n_minus', False): + return + + selected = [] + deselected = [] + + for item in items: + is_destructive = item.get_closest_marker('destructive') + # Deselect Destructive tests and tests without capsule_factory fixture + if 'capsule_factory' not in item.fixturenames or is_destructive: + deselected.append(item) + continue + # Ignoring all puppet tests as they are destructive in nature + # and needs its own satellite for verification + if 'session_puppet_enabled_sat' in item.fixturenames: + deselected.append(item) + continue + # Ignoring all satellite maintain tests as they are destructive in nature + # Also dont need them in nminus testing as its not integration testing + if 'sat_maintain' in item.fixturenames and 'satellite' in item.callspec.params.values(): + deselected.append(item) + continue + selected.append(item) + + config.hook.pytest_deselected(items=deselected) + items[:] = selected + + +def pytest_sessionfinish(session, exitstatus): + # Unregister the capsule from CDN after all tests + if session.config.option.n_minus and not session.config.option.collectonly: + caps = Capsule.get_host_by_hostname(hostname=settings.capsule.hostname) + caps.unregister() diff --git a/pytest_plugins/fixture_markers.py b/pytest_plugins/fixture_markers.py index 951be635ac6..909e7a53634 100644 --- a/pytest_plugins/fixture_markers.py +++ b/pytest_plugins/fixture_markers.py @@ -5,11 +5,13 @@ TARGET_FIXTURES = [ 'rhel_contenthost', + 'module_rhel_contenthost', 'content_hosts', 'module_provisioning_rhel_content', 'capsule_provisioning_rhel_content', 'module_sync_kickstart_content', 'rex_contenthost', + 'rex_contenthosts', ] @@ -17,7 +19,7 @@ def pytest_generate_tests(metafunc): content_host_fixture = ''.join([i for i in TARGET_FIXTURES if i in metafunc.fixturenames]) if content_host_fixture in metafunc.fixturenames: function_marks = getattr(metafunc.function, 'pytestmark', []) - no_containers = any('no_containers' == mark.name for mark in function_marks) + no_containers = any(mark.name == 'no_containers' for mark in function_marks) # process eventual rhel_version_list markers matchers = [i.args for i in function_marks if i.name == 'rhel_ver_list'] list_params = [] @@ -87,6 +89,7 @@ def chost_rhelver(params): for param in params: if 'contenthost' in param: return params[param].get('rhel_version') + return None content_host_fixture_names = [m[0] for m in getmembers(contenthosts, isfunction)] for item in items: diff --git a/pytest_plugins/logging_hooks.py b/pytest_plugins/logging_hooks.py index 9cd7ea4c84f..c15136a647a 100644 --- a/pytest_plugins/logging_hooks.py +++ b/pytest_plugins/logging_hooks.py @@ -1,3 +1,4 @@ +import contextlib import logging import logzero @@ -13,10 +14,8 @@ robottelo_log_file, ) -try: +with contextlib.suppress(ImportError): from pytest_reportportal import RPLogger, RPLogHandler -except ImportError: - pass @pytest.fixture(autouse=True, scope='session') @@ -37,37 +36,36 @@ def configure_logging(request, worker_id): if use_rp_logger: logging.setLoggerClass(RPLogger) - if is_xdist_worker(request): - if f'{worker_id}' not in [h.get_name() for h in logger.handlers]: - # Track the core logger's file handler level, set it in case core logger wasn't set - worker_log_level = 'INFO' - handlers_to_remove = [ - h - for h in logger.handlers - if isinstance(h, logging.FileHandler) - and getattr(h, 'baseFilename', None) == str(robottelo_log_file) - ] - for handler in handlers_to_remove: - logger.removeHandler(handler) - worker_log_level = handler.level - worker_handler = logging.FileHandler( - robottelo_log_dir.joinpath(f'robottelo_{worker_id}.log') - ) - worker_handler.set_name(f'{worker_id}') - worker_handler.setFormatter(worker_formatter) - worker_handler.setLevel(worker_log_level) - logger.addHandler(worker_handler) - broker_log_setup( - level=logging_yaml.broker.level, - file_level=logging_yaml.broker.fileLevel, - formatter=worker_formatter, - path=robottelo_log_dir.joinpath(f'robottelo_{worker_id}.log'), - ) + if is_xdist_worker(request) and f'{worker_id}' not in [h.get_name() for h in logger.handlers]: + # Track the core logger's file handler level, set it in case core logger wasn't set + worker_log_level = 'INFO' + handlers_to_remove = [ + h + for h in logger.handlers + if isinstance(h, logging.FileHandler) + and getattr(h, 'baseFilename', None) == str(robottelo_log_file) + ] + for handler in handlers_to_remove: + logger.removeHandler(handler) + worker_log_level = handler.level + worker_handler = logging.FileHandler( + robottelo_log_dir.joinpath(f'robottelo_{worker_id}.log') + ) + worker_handler.set_name(f'{worker_id}') + worker_handler.setFormatter(worker_formatter) + worker_handler.setLevel(worker_log_level) + logger.addHandler(worker_handler) + broker_log_setup( + level=logging_yaml.broker.level, + file_level=logging_yaml.broker.fileLevel, + formatter=worker_formatter, + path=robottelo_log_dir.joinpath(f'robottelo_{worker_id}.log'), + ) - if use_rp_logger: - rp_handler = RPLogHandler(request.node.config.py_test_service) - rp_handler.setFormatter(worker_formatter) - # logger.addHandler(rp_handler) + if use_rp_logger: + rp_handler = RPLogHandler(request.node.config.py_test_service) + rp_handler.setFormatter(worker_formatter) + # logger.addHandler(rp_handler) def pytest_runtest_logstart(nodeid, location): diff --git a/pytest_plugins/markers.py b/pytest_plugins/markers.py index abf54997bd8..e5d9855a179 100644 --- a/pytest_plugins/markers.py +++ b/pytest_plugins/markers.py @@ -24,6 +24,8 @@ def pytest_configure(config): "no_containers: Disable container hosts from being used in favor of VMs", "include_capsule: For satellite-maintain tests to run on Satellite and Capsule both", "capsule_only: For satellite-maintain tests to run only on Capsules", + "manifester: Tests that require manifester", + "ldap: Tests related to ldap authentication", ] markers.extend(module_markers()) for marker in markers: diff --git a/pytest_plugins/metadata_markers.py b/pytest_plugins/metadata_markers.py index 57b12aa5c1f..59b1e6c9e56 100644 --- a/pytest_plugins/metadata_markers.py +++ b/pytest_plugins/metadata_markers.py @@ -7,9 +7,22 @@ from robottelo.config import settings from robottelo.hosts import get_sat_rhel_version from robottelo.logging import collection_logger as logger +from robottelo.utils.issue_handlers.jira import are_any_jira_open FMT_XUNIT_TIME = '%Y-%m-%dT%H:%M:%S' IMPORTANCE_LEVELS = [] +selected = [] +deselected = [] + + +def parse_comma_separated_list(option_value): + if isinstance(option_value, str): + if option_value.lower() == 'true': + return True + if option_value.lower() == 'false': + return False + return [item.strip() for item in option_value.split(',')] + return None def pytest_addoption(parser): @@ -26,6 +39,25 @@ def pytest_addoption(parser): '--team', help='Comma separated list of teams to include in test collection', ) + parser.addoption( + '--blocked-by', + type=parse_comma_separated_list, + nargs='?', + const=True, + default=True, + help='Comma separated list of Jiras to collect tests matching BlockedBy testimony marker. ' + 'If no issue is provided all the tests with BlockedBy testimony marker will be processed ' + 'and deselected if any issue is open.', + ) + parser.addoption( + '--verifies-issues', + type=parse_comma_separated_list, + nargs='?', + const=True, + default=False, + help='Comma separated list of Jiras to collect tests matching Verifies testimony marker. ' + 'If no issue is provided all the tests with Verifies testimony marker will be selected.', + ) def pytest_configure(config): @@ -34,6 +66,8 @@ def pytest_configure(config): 'importance: CaseImportance testimony token, use --importance to filter', 'component: Component testimony token, use --component to filter', 'team: Team testimony token, use --team to filter', + 'blocked_by: BlockedBy testimony token, use --blocked-by to filter', + 'verifies_issues: Verifies testimony token, use --verifies_issues to filter', ]: config.addinivalue_line("markers", marker) @@ -56,6 +90,57 @@ def pytest_configure(config): re.IGNORECASE, ) +blocked_by_regex = re.compile( + # To match :BlockedBy: SAT-32932 + r'\s*:BlockedBy:\s*(?P.*\S*)', + re.IGNORECASE, +) + +verifies_regex = re.compile( + # To match :Verifies: SAT-32932 + r'\s*:Verifies:\s*(?P.*\S*)', + re.IGNORECASE, +) + + +def handle_verification_issues(item, verifies_marker, verifies_issues): + """Handles the logic for deselecting tests based on Verifies testimony token + and --verifies-issues pytest option. + """ + if verifies_issues: + if not verifies_marker: + log_and_deselect(item, '--verifies-issues') + return False + if isinstance(verifies_issues, list): + verifies_args = verifies_marker.args[0] + if all(issue not in verifies_issues for issue in verifies_args): + log_and_deselect(item, '--verifies-issues') + return False + return True + + +def handle_blocked_by(item, blocked_by_marker, blocked_by): + """Handles the logic for deselecting tests based on BlockedBy testimony token + and --blocked-by pytest option. + """ + if isinstance(blocked_by, list): + if not blocked_by_marker: + log_and_deselect(item, '--blocked-by') + return False + if all(issue not in blocked_by for issue in blocked_by_marker.args[0]): + log_and_deselect(item, '--blocked-by') + return False + elif isinstance(blocked_by, bool) and blocked_by_marker: + if blocked_by and are_any_jira_open(blocked_by_marker.args[0]): + log_and_deselect(item, '--blocked-by') + return False + return True + + +def log_and_deselect(item, option): + logger.debug(f'Deselected test {item.nodeid} due to "{option}" pytest option.') + deselected.append(item) + @pytest.hookimpl(tryfirst=True) def pytest_collection_modifyitems(items, config): @@ -81,9 +166,8 @@ def pytest_collection_modifyitems(items, config): importance = [i for i in (config.getoption('importance') or '').split(',') if i != ''] component = [c for c in (config.getoption('component') or '').split(',') if c != ''] team = [a.lower() for a in (config.getoption('team') or '').split(',') if a != ''] - - selected = [] - deselected = [] + verifies_issues = config.getoption('verifies_issues') + blocked_by = config.getoption('blocked_by') logger.info('Processing test items to add testimony token markers') for item in items: item.user_properties.append( @@ -100,6 +184,8 @@ def pytest_collection_modifyitems(items, config): for d in map(inspect.getdoc, (item.function, getattr(item, 'cls', None), item.module)) if d is not None ] + blocked_by_marks_to_add = [] + verifies_marks_to_add = [] for docstring in item_docstrings: item_mark_names = [m.name for m in item.iter_markers()] # Add marker starting at smallest docstring scope @@ -113,6 +199,18 @@ def pytest_collection_modifyitems(items, config): doc_team = team_regex.findall(docstring) if doc_team and 'team' not in item_mark_names: item.add_marker(pytest.mark.team(doc_team[0].lower())) + doc_verifies = verifies_regex.findall(docstring) + if doc_verifies and 'verifies_issues' not in item_mark_names: + verifies_marks_to_add.extend(str(b.strip()) for b in doc_verifies[-1].split(',')) + doc_blocked_by = blocked_by_regex.findall(docstring) + if doc_blocked_by and 'blocked_by' not in item_mark_names: + blocked_by_marks_to_add.extend( + str(b.strip()) for b in doc_blocked_by[-1].split(',') + ) + if blocked_by_marks_to_add: + item.add_marker(pytest.mark.blocked_by(blocked_by_marks_to_add)) + if verifies_marks_to_add: + item.add_marker(pytest.mark.verifies_issues(verifies_marks_to_add)) # add markers as user_properties so they are recorded in XML properties of the report # pytest-ibutsu will include user_properties dict in testresult metadata @@ -169,7 +267,16 @@ def pytest_collection_modifyitems(items, config): deselected.append(item) continue - selected.append(item) + if verifies_issues or blocked_by: + # Filter tests based on --verifies-issues and --blocked-by pytest options + # and Verifies and BlockedBy testimony tokens. + verifies_marker = item.get_closest_marker('verifies_issues', False) + blocked_by_marker = item.get_closest_marker('blocked_by', False) + if not handle_verification_issues(item, verifies_marker, verifies_issues): + continue + if not handle_blocked_by(item, blocked_by_marker, blocked_by): + continue + selected.append(item) # selected will be empty if no filter option was passed, defaulting to full items list items[:] = selected if deselected else items diff --git a/pytest_plugins/rerun_rp/rerun_rp.py b/pytest_plugins/rerun_rp/rerun_rp.py index 5b14873a885..7007880ce4b 100644 --- a/pytest_plugins/rerun_rp/rerun_rp.py +++ b/pytest_plugins/rerun_rp/rerun_rp.py @@ -123,7 +123,7 @@ def pytest_collection_modifyitems(items, config): test_args['status'].append('SKIPPED') if fail_args: test_args['status'].append('FAILED') - if not fail_args == 'all': + if fail_args != 'all': defect_types = fail_args.split(',') allowed_args = [*rp.defect_types.keys()] if not set(defect_types).issubset(set(allowed_args)): diff --git a/pytest_plugins/sanity_plugin.py b/pytest_plugins/sanity_plugin.py index f955d9cf4ca..1d93a4b45f3 100644 --- a/pytest_plugins/sanity_plugin.py +++ b/pytest_plugins/sanity_plugin.py @@ -43,10 +43,12 @@ def pytest_collection_modifyitems(session, items, config): deselected.append(item) continue # Remove parametrization from organization test - if 'test_positive_create_with_name_and_description' in item.name: - if 'alphanumeric' not in item.name: - deselected.append(item) - continue + if ( + 'test_positive_create_with_name_and_description' in item.name + and 'alphanumeric' not in item.name + ): + deselected.append(item) + continue # Else select selected.append(item) diff --git a/pytest_plugins/settings_skip.py b/pytest_plugins/settings_skip.py index 6ee053adee5..e345338d5d8 100644 --- a/pytest_plugins/settings_skip.py +++ b/pytest_plugins/settings_skip.py @@ -22,7 +22,7 @@ def pytest_runtest_setup(item): skip_marker = item.get_closest_marker('skip_if_not_set', None) if skip_marker and skip_marker.args: options_set = {arg.upper() for arg in skip_marker.args} - settings_set = {key for key in settings.keys() if not key.endswith('_FOR_DYNACONF')} + settings_set = {key for key in settings if not key.endswith('_FOR_DYNACONF')} if not options_set.issubset(settings_set): invalid = options_set.difference(settings_set) raise ValueError( diff --git a/pytest_plugins/upgrade/scenario_workers.py b/pytest_plugins/upgrade/scenario_workers.py index 099ff1c0fe3..9deeb0ddf25 100644 --- a/pytest_plugins/upgrade/scenario_workers.py +++ b/pytest_plugins/upgrade/scenario_workers.py @@ -23,6 +23,7 @@ def save_worker_hostname(test_name, target_sat): def shared_workers(): if json_file.exists(): return json.loads(json_file.read_text()) + return None def get_worker_hostname_from_testname(test_name, shared_workers): diff --git a/pytest_plugins/video_cleanup.py b/pytest_plugins/video_cleanup.py index 08bbf5721d9..320864b7060 100644 --- a/pytest_plugins/video_cleanup.py +++ b/pytest_plugins/video_cleanup.py @@ -64,15 +64,14 @@ def pytest_runtest_makereport(item): 'longrepr': str(report.longrepr), } ) - if report.when == "teardown": - if item.nodeid in test_results: - result_info = test_results[item.nodeid] - if result_info.outcome == 'passed': - report.user_properties = [ - (key, value) for key, value in report.user_properties if key != 'video_url' - ] - session_id_tuple = next( - (t for t in report.user_properties if t[0] == 'session_id'), None - ) - session_id = session_id_tuple[1] if session_id_tuple else None - _clean_video(session_id, item.nodeid) + if report.when == "teardown" and item.nodeid in test_results: + result_info = test_results[item.nodeid] + if result_info.outcome == 'passed': + report.user_properties = [ + (key, value) for key, value in report.user_properties if key != 'video_url' + ] + session_id_tuple = next( + (t for t in report.user_properties if t[0] == 'session_id'), None + ) + session_id = session_id_tuple[1] if session_id_tuple else None + _clean_video(session_id, item.nodeid) diff --git a/requirements-optional.txt b/requirements-optional.txt index 3971b77f3d9..3b38e173691 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -1,11 +1,11 @@ # For running tests and checking code quality using these modules. flake8==7.0.0 -pytest-cov==4.1.0 -redis==5.0.2 -pre-commit==3.6.2 +pytest-cov==5.0.0 +redis==5.0.4 +pre-commit==3.7.0 # For generating documentation. -sphinx==7.2.6 +sphinx==7.3.7 sphinx-autoapi==3.0.0 # For 'manage' interactive shell diff --git a/requirements.txt b/requirements.txt index d24cbfaee2c..2adc13eb18f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,40 +1,34 @@ # Version updates managed by dependabot +apypie==0.4.0 betelgeuse==1.11.0 -# broker[docker]==0.4.1 - Temporarily disabled, see below +broker[docker]==0.4.9 cryptography==42.0.5 -deepdiff==6.7.1 -docker==7.0.0 # Temporary until Broker is back on PyPi -dynaconf[vault]==3.2.4 -fauxfactory==3.1.0 +deepdiff==7.0.1 +dynaconf[vault]==3.2.5 +fauxfactory==3.1.1 jinja2==3.1.3 manifester==0.0.14 navmazing==1.2.2 -paramiko==3.4.0 # Temporary until Broker is back on PyPi productmd==1.38 pyotp==2.9.0 python-box==7.1.1 -pytest==8.1.0 -pytest-order==1.2.0 +pytest==8.1.1 +pytest-order==1.2.1 pytest-services==2.2.1 -pytest-mock==3.12.0 -pytest-reportportal==5.4.0 +pytest-mock==3.14.0 +pytest-reportportal==5.4.1 pytest-xdist==3.5.0 pytest-fixturecollection==0.1.2 pytest-ibutsu==2.2.4 PyYAML==6.0.1 requests==2.31.0 tenacity==8.2.3 -testimony==2.3.0 +testimony==2.4.0 wait-for==1.2.0 wrapanapi==3.6.0 # Get airgun, nailgun and upgrade from master -git+https://github.com/SatelliteQE/airgun.git@master#egg=airgun -git+https://github.com/SatelliteQE/nailgun.git@master#egg=nailgun -# Broker currently is unable to push to PyPi due to [1] and [2] -# In the meantime, we install directly from the repo -# [1] - https://github.com/ParallelSSH/ssh2-python/issues/193 -# [2] - https://github.com/pypi/warehouse/issues/7136 -git+https://github.com/SatelliteQE/broker.git@0.4.5#egg=broker +airgun @ git+https://github.com/SatelliteQE/airgun.git@master#egg=airgun +nailgun @ git+https://github.com/SatelliteQE/nailgun.git@master#egg=nailgun --editable . diff --git a/robottelo/cli/base.py b/robottelo/cli/base.py index 247d17985e1..d276cd741fa 100644 --- a/robottelo/cli/base.py +++ b/robottelo/cli/base.py @@ -139,9 +139,7 @@ def delete_parameter(cls, options=None, timeout=None): cls.command_sub = 'delete-parameter' - result = cls.execute(cls._construct_command(options), ignore_stderr=False, timeout=timeout) - - return result + return cls.execute(cls._construct_command(options), ignore_stderr=False, timeout=timeout) @classmethod def dump(cls, options=None, timeout=None): @@ -151,9 +149,7 @@ def dump(cls, options=None, timeout=None): cls.command_sub = 'dump' - result = cls.execute(cls._construct_command(options), ignore_stderr=False, timeout=timeout) - - return result + return cls.execute(cls._construct_command(options), ignore_stderr=False, timeout=timeout) @classmethod def _get_username_password(cls, username=None, password=None): @@ -204,23 +200,21 @@ def execute( command, ) response = ssh.command( - cmd.encode('utf-8'), + cmd, hostname=hostname or cls.hostname or settings.server.hostname, output_format=output_format, timeout=timeout, ) if return_raw_response: return response - else: - return cls._handle_response(response, ignore_stderr=ignore_stderr) + return cls._handle_response(response, ignore_stderr=ignore_stderr) @classmethod def sm_execute(cls, command, hostname=None, timeout=None, **kwargs): """Executes the satellite-maintain cli commands on the server via ssh""" env_var = kwargs.get('env_var') or '' client = get_client(hostname=hostname or cls.hostname) - result = client.execute(f'{env_var} satellite-maintain {command}', timeout=timeout) - return result + return client.execute(f'{env_var} satellite-maintain {command}', timeout=timeout) @classmethod def exists(cls, options=None, search=None): @@ -375,6 +369,4 @@ def _construct_command(cls, options=None): if isinstance(val, list): val = ','.join(str(el) for el in val) tail += f' --{key}="{val}"' - cmd = f"{cls.command_base or ''} {cls.command_sub or ''} {tail.strip()} {cls.command_end or ''}" - - return cmd + return f"{cls.command_base or ''} {cls.command_sub or ''} {tail.strip()} {cls.command_end or ''}" diff --git a/robottelo/cli/capsule.py b/robottelo/cli/capsule.py index 2f27ba2f5c0..bb6825cc783 100644 --- a/robottelo/cli/capsule.py +++ b/robottelo/cli/capsule.py @@ -35,9 +35,7 @@ def content_add_lifecycle_environment(cls, options): cls.command_sub = 'content add-lifecycle-environment' - result = cls.execute(cls._construct_command(options), output_format='csv') - - return result + return cls.execute(cls._construct_command(options), output_format='csv') @classmethod def content_available_lifecycle_environments(cls, options): @@ -45,9 +43,7 @@ def content_available_lifecycle_environments(cls, options): cls.command_sub = 'content available-lifecycle-environments' - result = cls.execute(cls._construct_command(options), output_format='csv') - - return result + return cls.execute(cls._construct_command(options), output_format='csv') @classmethod def content_info(cls, options): @@ -55,9 +51,7 @@ def content_info(cls, options): cls.command_sub = 'content info' - result = cls.execute(cls._construct_command(options), output_format='json') - - return result + return cls.execute(cls._construct_command(options), output_format='json') @classmethod def content_lifecycle_environments(cls, options): @@ -65,9 +59,7 @@ def content_lifecycle_environments(cls, options): cls.command_sub = 'content lifecycle-environments' - result = cls.execute(cls._construct_command(options), output_format='csv') - - return result + return cls.execute(cls._construct_command(options), output_format='csv') @classmethod def content_remove_lifecycle_environment(cls, options): @@ -75,9 +67,7 @@ def content_remove_lifecycle_environment(cls, options): cls.command_sub = 'content remove-lifecycle-environment' - result = cls.execute(cls._construct_command(options), output_format='csv') - - return result + return cls.execute(cls._construct_command(options), output_format='csv') @classmethod def content_synchronization_status(cls, options): @@ -85,9 +75,7 @@ def content_synchronization_status(cls, options): cls.command_sub = 'content synchronization-status' - result = cls.execute(cls._construct_command(options), output_format='csv') - - return result + return cls.execute(cls._construct_command(options), output_format='csv') @classmethod def content_synchronize(cls, options, return_raw_response=None, timeout=3600000): @@ -95,7 +83,7 @@ def content_synchronize(cls, options, return_raw_response=None, timeout=3600000) cls.command_sub = 'content synchronize' - result = cls.execute( + return cls.execute( cls._construct_command(options), output_format='csv', ignore_stderr=True, @@ -103,17 +91,13 @@ def content_synchronize(cls, options, return_raw_response=None, timeout=3600000) timeout=timeout, ) - return result - @classmethod def content_update_counts(cls, options): """Trigger content counts update.""" cls.command_sub = 'content update-counts' - result = cls.execute(cls._construct_command(options), output_format='json') - - return result + return cls.execute(cls._construct_command(options), output_format='json') @classmethod def import_classes(cls, options): @@ -121,9 +105,7 @@ def import_classes(cls, options): cls.command_sub = 'import-classes' - result = cls.execute(cls._construct_command(options), output_format='csv') - - return result + return cls.execute(cls._construct_command(options), output_format='csv') @classmethod def refresh_features(cls, options): @@ -131,6 +113,4 @@ def refresh_features(cls, options): cls.command_sub = 'refresh-features' - result = cls.execute(cls._construct_command(options), output_format='csv') - - return result + return cls.execute(cls._construct_command(options), output_format='csv') diff --git a/robottelo/cli/contentview.py b/robottelo/cli/contentview.py index fb8001f89d7..17732f37617 100644 --- a/robottelo/cli/contentview.py +++ b/robottelo/cli/contentview.py @@ -43,7 +43,7 @@ class ContentViewFilterRule(Base): command_base = 'content-view filter rule' @classmethod - def create(cls, options=None): + def create(cls, options=None, timeout=None): """Create a content-view filter rule""" if ( not options @@ -55,7 +55,7 @@ def create(cls, options=None): ' "content-view-filter" or "content-view-filter-id".' ) cls.command_sub = 'create' - result = cls.execute(cls._construct_command(options), output_format='csv') + result = cls.execute(cls._construct_command(options), output_format='csv', timeout=timeout) # Extract new CV filter rule ID if it was successfully created if len(result) > 0 and 'id' in result[0]: diff --git a/robottelo/cli/hammer.py b/robottelo/cli/hammer.py index 0471754002f..4e35b0c7612 100644 --- a/robottelo/cli/hammer.py +++ b/robottelo/cli/hammer.py @@ -26,10 +26,10 @@ def _normalize_obj(obj): """ if isinstance(obj, dict): return {_normalize(k): _normalize_obj(v) for k, v in obj.items()} - elif isinstance(obj, list): + if isinstance(obj, list): return [_normalize_obj(v) for v in obj] # doing this to conform to csv parser - elif isinstance(obj, int) and not isinstance(obj, bool): + if isinstance(obj, int) and not isinstance(obj, bool): return str(obj) return obj @@ -48,8 +48,8 @@ def parse_csv(output): """Parse CSV output from Hammer CLI and convert it to python dictionary.""" # ignore warning about puppet and ostree deprecation output.replace('Puppet and OSTree will no longer be supported in Katello 3.16\n', '') - is_rex = True if 'Job invocation' in output else False - is_pkg_list = True if 'Nvra' in output else False + is_rex = 'Job invocation' in output + is_pkg_list = 'Nvra' in output # Validate if the output is eligible for CSV conversions else return as it is if not is_csv(output) and not is_rex and not is_pkg_list: return output @@ -200,7 +200,7 @@ def parse_info(output): if line.startswith(' '): # sub-properties are indented # values are separated by ':' or '=>', but not by '::' which can be # entity name like 'test::params::keys' - if line.find(':') != -1 and not line.find('::') != -1: + if line.find(':') != -1 and line.find('::') == -1: key, value = line.lstrip().split(":", 1) elif line.find('=>') != -1 and len(line.lstrip().split(" =>", 1)) == 2: key, value = line.lstrip().split(" =>", 1) diff --git a/robottelo/cli/host.py b/robottelo/cli/host.py index 6ec096cfe10..dc45c9db887 100644 --- a/robottelo/cli/host.py +++ b/robottelo/cli/host.py @@ -215,9 +215,7 @@ def reboot(cls, options=None): cls.command_sub = 'reboot' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) @classmethod def reports(cls, options=None): @@ -268,9 +266,7 @@ def start(cls, options=None): cls.command_sub = 'start' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) @classmethod def status(cls, options=None): @@ -290,9 +286,7 @@ def status(cls, options=None): cls.command_sub = 'status' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) @classmethod def stop(cls, options=None): @@ -313,9 +307,7 @@ def stop(cls, options=None): cls.command_sub = 'stop' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) @classmethod def subscription_register(cls, options=None): @@ -486,10 +478,10 @@ class HostInterface(Base): command_base = 'host interface' @classmethod - def create(cls, options=None): + def create(cls, options=None, timeout=None): """Create new network interface for host""" cls.command_sub = 'create' - cls.execute(cls._construct_command(options), output_format='csv') + cls.execute(cls._construct_command(options), output_format='csv', timeout=timeout) class HostTraces(Base): diff --git a/robottelo/cli/job_invocation.py b/robottelo/cli/job_invocation.py index e1aeacdc84e..fe02dae4d74 100644 --- a/robottelo/cli/job_invocation.py +++ b/robottelo/cli/job_invocation.py @@ -32,7 +32,7 @@ def get_output(cls, options): return cls.execute(cls._construct_command(options)) @classmethod - def create(cls, options): + def create(cls, options, timeout=None): """Create a job""" cls.command_sub = 'create' - return cls.execute(cls._construct_command(options), output_format='csv') + return cls.execute(cls._construct_command(options), output_format='csv', timeout=timeout) diff --git a/robottelo/cli/lifecycleenvironment.py b/robottelo/cli/lifecycleenvironment.py index 72435669b62..c22d2268746 100644 --- a/robottelo/cli/lifecycleenvironment.py +++ b/robottelo/cli/lifecycleenvironment.py @@ -29,9 +29,7 @@ class LifecycleEnvironment(Base): @classmethod def list(cls, options=None, per_page=False): - result = super().list(options, per_page=per_page) - - return result + return super().list(options, per_page=per_page) @classmethod def paths(cls, options=None): diff --git a/robottelo/cli/operatingsys.py b/robottelo/cli/operatingsys.py index 1a1a63bdebd..8bf4411d56d 100644 --- a/robottelo/cli/operatingsys.py +++ b/robottelo/cli/operatingsys.py @@ -45,9 +45,7 @@ def add_architecture(cls, options=None): cls.command_sub = 'add-architecture' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) @classmethod def add_provisioning_template(cls, options=None): @@ -57,9 +55,7 @@ def add_provisioning_template(cls, options=None): cls.command_sub = 'add-provisioning-template' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) @classmethod def add_ptable(cls, options=None): @@ -69,9 +65,7 @@ def add_ptable(cls, options=None): cls.command_sub = 'add-ptable' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) @classmethod def remove_architecture(cls, options=None): @@ -81,9 +75,7 @@ def remove_architecture(cls, options=None): cls.command_sub = 'remove-architecture' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) @classmethod def remove_provisioning_template(cls, options=None): @@ -93,9 +85,7 @@ def remove_provisioning_template(cls, options=None): cls.command_sub = 'remove-provisioning-template' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) @classmethod def remove_ptable(cls, options=None): @@ -105,6 +95,4 @@ def remove_ptable(cls, options=None): cls.command_sub = 'remove-ptable ' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) diff --git a/robottelo/cli/product.py b/robottelo/cli/product.py index efbf7ed09e1..7a7f03eeff4 100644 --- a/robottelo/cli/product.py +++ b/robottelo/cli/product.py @@ -39,9 +39,7 @@ def remove_sync_plan(cls, options=None): cls.command_sub = 'remove-sync-plan' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) @classmethod def set_sync_plan(cls, options=None): @@ -51,9 +49,7 @@ def set_sync_plan(cls, options=None): cls.command_sub = 'set-sync-plan' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) @classmethod def synchronize(cls, options=None): @@ -69,6 +65,4 @@ def update_proxy(cls, options=None): cls.command_sub = 'update-proxy' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) diff --git a/robottelo/cli/report_template.py b/robottelo/cli/report_template.py index 962c5929bc7..50646e3cd4b 100644 --- a/robottelo/cli/report_template.py +++ b/robottelo/cli/report_template.py @@ -36,7 +36,7 @@ class ReportTemplate(Base): command_base = 'report-template' @classmethod - def create(cls, options=None): + def create(cls, options=None, timeout=None): """ Creates a new record using the arguments passed via dictionary. """ @@ -73,7 +73,7 @@ def create(cls, options=None): options['file'] = layout - result = cls.execute(cls._construct_command(options), output_format='csv') + result = cls.execute(cls._construct_command(options), output_format='csv', timeout=timeout) # Extract new object ID if it was successfully created if len(result) > 0 and 'id' in result[0]: diff --git a/robottelo/cli/repository.py b/robottelo/cli/repository.py index 94fa8baa180..d5ac3caf7a1 100644 --- a/robottelo/cli/repository.py +++ b/robottelo/cli/repository.py @@ -31,12 +31,12 @@ class Repository(Base): command_requires_org = True @classmethod - def create(cls, options=None): + def create(cls, options=None, timeout=None): """Create a custom repository""" cls.command_requires_org = False try: - result = super().create(options) + result = super().create(options, timeout) finally: cls.command_requires_org = True diff --git a/robottelo/cli/sm_advanced.py b/robottelo/cli/sm_advanced.py index 42ba037b811..4c2b4ffb33d 100644 --- a/robottelo/cli/sm_advanced.py +++ b/robottelo/cli/sm_advanced.py @@ -23,13 +23,6 @@ backup-online-safety-confirmation Data consistency warning backup-prepare-directory Prepare backup Directory backup-pulp Backup Pulp data - backup-snapshot-clean-mount Remove the snapshot mount points - backup-snapshot-logical-volume-confirmation Check if backup is on different LV then the source - backup-snapshot-mount-candlepin-db Create and mount snapshot of Candlepin DB - backup-snapshot-mount-foreman-db Create and mount snapshot of Foreman DB - backup-snapshot-mount-pulp Create and mount snapshot of Pulp data - backup-snapshot-mount-pulpcore-db Create and mount snapshot of Pulpcore DB - backup-snapshot-prepare-mount Prepare mount point for the snapshot content-migration-reset Reset the Pulp 2 to Pulp 3 migration data (pre-switchover) content-migration-stats Retrieve Pulp 2 to Pulp 3 migration statistics content-prepare Prepare content for Pulp 3 diff --git a/robottelo/cli/sm_backup.py b/robottelo/cli/sm_backup.py index 8da59080151..ef703134cfa 100644 --- a/robottelo/cli/sm_backup.py +++ b/robottelo/cli/sm_backup.py @@ -9,7 +9,6 @@ Subcommands: online Keep services online during backup offline Shut down services to preserve consistent backup - snapshot Use snapshots of the databases to create backup Options: -h, --help print help @@ -24,7 +23,7 @@ class Backup(Base): @classmethod def run_backup(cls, backup_dir='/tmp/', backup_type='online', options=None, timeout=None): - """Build satellite-maintain backup online/offline/snapshot""" + """Build satellite-maintain backup online/offline""" cls.command_sub = backup_type cls.command_end = backup_dir options = options or {} diff --git a/robottelo/cli/srpm.py b/robottelo/cli/srpm.py index b0e71d8969d..62578bbaa69 100644 --- a/robottelo/cli/srpm.py +++ b/robottelo/cli/srpm.py @@ -25,15 +25,11 @@ def info(cls, options=None): """Show a SRPM Info""" cls.command_sub = 'info' - result = cls.execute(cls._construct_command(options), output_format='csv') - - return result + return cls.execute(cls._construct_command(options), output_format='csv') @classmethod def list(cls, options=None): """List SRPMs""" cls.command_sub = 'list' - result = cls.execute(cls._construct_command(options), output_format='csv') - - return result + return cls.execute(cls._construct_command(options), output_format='csv') diff --git a/robottelo/cli/syncplan.py b/robottelo/cli/syncplan.py index 3f8b9b8b347..9894335ab05 100644 --- a/robottelo/cli/syncplan.py +++ b/robottelo/cli/syncplan.py @@ -17,9 +17,20 @@ update """ from robottelo.cli.base import Base +from robottelo.exceptions import CLIError class SyncPlan(Base): """Manipulates Katello engine's sync-plan command.""" command_base = 'sync-plan' + + @classmethod + def create(cls, options=None, timeout=None): + """Create a SyncPlan""" + cls.command_sub = 'create' + + if options.get('interval') == 'custom cron' and options.get('cron-expression') is None: + raise CLIError('Missing "cron-expression" option for "custom cron" interval.') + + return super().create(options, timeout) diff --git a/robottelo/cli/template.py b/robottelo/cli/template.py index 4dbe709c33b..3729fc4bf76 100644 --- a/robottelo/cli/template.py +++ b/robottelo/cli/template.py @@ -46,18 +46,14 @@ def add_operatingsystem(cls, options=None): """Adds operating system, requires "id" and "operatingsystem-id".""" cls.command_sub = 'add-operatingsystem' - result = cls.execute(cls._construct_command(options), output_format='csv') - - return result + return cls.execute(cls._construct_command(options), output_format='csv') @classmethod def remove_operatingsystem(cls, options=None): """Remove operating system, requires "id" and "operatingsystem-id".""" cls.command_sub = 'remove-operatingsystem' - result = cls.execute(cls._construct_command(options), output_format='csv') - - return result + return cls.execute(cls._construct_command(options), output_format='csv') @classmethod def clone(cls, options=None): diff --git a/robottelo/cli/template_input.py b/robottelo/cli/template_input.py index cfe548384c3..9b861dbf9a9 100644 --- a/robottelo/cli/template_input.py +++ b/robottelo/cli/template_input.py @@ -26,7 +26,7 @@ class TemplateInput(Base): command_base = 'template-input' @classmethod - def create(cls, options=None): + def create(cls, options=None, timeout=None): """ Creates a new record using the arguments passed via dictionary. """ @@ -36,7 +36,7 @@ def create(cls, options=None): if options is None: options = {} - result = cls.execute(cls._construct_command(options), output_format='csv') + result = cls.execute(cls._construct_command(options), output_format='csv', timeout=timeout) # Extract new object ID if it was successfully created if len(result) > 0 and 'id' in result[0]: diff --git a/robottelo/cli/template_sync.py b/robottelo/cli/template_sync.py index 6da3b683653..72ee70fa00f 100644 --- a/robottelo/cli/template_sync.py +++ b/robottelo/cli/template_sync.py @@ -50,15 +50,11 @@ def exports(cls, options=None): """Export Satellite Templates to Git/Local Directory.""" cls.command_base = 'export-templates' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) @classmethod def imports(cls, options=None): """Import Satellite Templates to Git/Local Directory.""" cls.command_base = 'import-templates' - result = cls.execute(cls._construct_command(options)) - - return result + return cls.execute(cls._construct_command(options)) diff --git a/robottelo/cli/usergroup.py b/robottelo/cli/usergroup.py index cd40ac96bc5..f29ad3f5573 100644 --- a/robottelo/cli/usergroup.py +++ b/robottelo/cli/usergroup.py @@ -148,10 +148,10 @@ def refresh(cls, options=None): return cls.execute(cls._construct_command(options), output_format='csv') @classmethod - def create(cls, options=None): + def create(cls, options=None, timeout=None): """Create external user group""" cls.command_sub = 'create' - result = cls.execute(cls._construct_command(options), output_format='csv') + result = cls.execute(cls._construct_command(options), output_format='csv', timeout=timeout) # External user group can only be fetched by specifying both id and # user group id it is linked to if len(result) > 0 and 'id' in result[0]: diff --git a/robottelo/cli/webhook.py b/robottelo/cli/webhook.py index 379555630c5..ea37b6c7779 100644 --- a/robottelo/cli/webhook.py +++ b/robottelo/cli/webhook.py @@ -22,7 +22,7 @@ class Webhook(Base): command_base = 'webhook' @classmethod - def create(cls, options=None): + def create(cls, options=None, timeout=None): """Create a webhook""" cls.command_sub = 'create' @@ -41,5 +41,4 @@ def create(cls, options=None): 'See See "hammer webhook create --help" for the list of supported methods' ) - result = super().create(options) - return result + return super().create(options, timeout) diff --git a/robottelo/config/__init__.py b/robottelo/config/__init__.py index 2d66d00054b..cca2258c7e6 100644 --- a/robottelo/config/__init__.py +++ b/robottelo/config/__init__.py @@ -183,7 +183,7 @@ def configure_airgun(): 'webdriver': settings.ui.webdriver, 'webdriver_binary': settings.ui.webdriver_binary, }, - 'webkaifuku': {'config': settings.ui.webkaifuku} or {}, + 'webkaifuku': {'config': settings.ui.webkaifuku}, } ) diff --git a/robottelo/config/validators.py b/robottelo/config/validators.py index f598821839d..1a6a6d7f4aa 100644 --- a/robottelo/config/validators.py +++ b/robottelo/config/validators.py @@ -189,6 +189,10 @@ must_exist=True, ), ], + jira=[ + Validator('jira.url', default='https://issues.redhat.com'), + Validator('jira.api_key', must_exist=True), + ], ldap=[ Validator( 'ldap.basedn', @@ -225,7 +229,12 @@ Validator( 'oscap.content_path', must_exist=True, - ) + ), + Validator( + 'oscap.profile', + default='security7', + must_exist=True, + ), ], osp=[ Validator( diff --git a/robottelo/constants/__init__.py b/robottelo/constants/__init__.py index 090ca4fcd88..b3cfbf8962f 100644 --- a/robottelo/constants/__init__.py +++ b/robottelo/constants/__init__.py @@ -263,6 +263,11 @@ class Colored(Box): 'rhel-8-for-x86_64-appstream-rpms', ) +OHSNAP_RHEL9_REPOS = ( + 'rhel-9-for-x86_64-baseos-rpms', + 'rhel-9-for-x86_64-appstream-rpms', +) + # On importing manifests, Red Hat repositories are listed like this: # Product -> RepositorySet -> Repository # We need to first select the Product, then the reposet and then the repos @@ -306,9 +311,9 @@ class Colored(Box): 'kickstart': { 'rhel6': 'Red Hat Enterprise Linux 6 Server (Kickstart)', 'rhel7': 'Red Hat Enterprise Linux 7 Server (Kickstart)', - 'rhel8': 'Red Hat Enterprise Linux 8 for x86_64 - BaseOS (Kickstart)', + 'rhel8_bos': 'Red Hat Enterprise Linux 8 for x86_64 - BaseOS (Kickstart)', 'rhel8_aps': 'Red Hat Enterprise Linux 8 for x86_64 - AppStream (Kickstart)', - 'rhel9': 'Red Hat Enterprise Linux 9 for x86_64 - BaseOS (Kickstart)', + 'rhel9_bos': 'Red Hat Enterprise Linux 9 for x86_64 - BaseOS (Kickstart)', 'rhel9_aps': 'Red Hat Enterprise Linux 9 for x86_64 - AppStream (Kickstart)', }, 'rhel8_bos': 'Red Hat Enterprise Linux 8 for x86_64 - BaseOS (RPMs)', @@ -411,6 +416,7 @@ class Colored(Box): 'reposet': REPOSET['rhsclient8'], 'product': PRDS['rhel8'], 'distro': 'rhel8', + 'releasever': None, 'key': PRODUCT_KEY_SAT_CLIENT, }, 'rhsclient9': { @@ -510,6 +516,7 @@ class Colored(Box): 'reposet': REPOSET['rhst8'], 'product': PRDS['rhel8'], 'distro': 'rhel8', + 'releasever': None, 'key': 'rhst', }, 'kickstart': { @@ -533,7 +540,7 @@ class Colored(Box): 'id': 'rhel-8-for-x86_64-baseos-kickstart', 'name': 'Red Hat Enterprise Linux 8 for x86_64 - BaseOS Kickstart 8.9', 'version': '8.9', - 'reposet': REPOSET['kickstart']['rhel8'], + 'reposet': REPOSET['kickstart']['rhel8_bos'], 'product': PRDS['rhel8'], 'distro': 'rhel8', }, @@ -549,7 +556,7 @@ class Colored(Box): 'id': 'rhel-9-for-x86_64-baseos-kickstart', 'name': 'Red Hat Enterprise Linux 9 for x86_64 - BaseOS Kickstart 9.3', 'version': '9.3', - 'reposet': REPOSET['kickstart']['rhel9'], + 'reposet': REPOSET['kickstart']['rhel9_bos'], 'product': PRDS['rhel9'], 'distro': 'rhel9', }, @@ -970,13 +977,6 @@ class Colored(Box): 'view_discovery_rules', ], 'Domain': ['view_domains', 'create_domains', 'edit_domains', 'destroy_domains'], - # 'Environment': [ - # 'view_environments', - # 'create_environments', - # 'edit_environments', - # 'destroy_environments', - # 'import_environments', - # ], 'ExternalUsergroup': [ 'view_external_usergroups', 'create_external_usergroups', @@ -1054,19 +1054,35 @@ class Colored(Box): 'destroy_hostgroups', 'play_roles_on_hostgroup', ], - # 'Puppetclass': [ - # 'view_puppetclasses', - # 'create_puppetclasses', - # 'edit_puppetclasses', - # 'destroy_puppetclasses', - # 'import_puppetclasses', - # ], - # 'PuppetclassLookupKey': [ - # 'view_external_parameters', - # 'create_external_parameters', - # 'edit_external_parameters', - # 'destroy_external_parameters', - # ], + 'ForemanPuppet::ConfigGroup': [ + 'view_config_groups', + 'create_config_groups', + 'edit_config_groups', + 'destroy_config_groups', + ], + 'ForemanPuppet::Environment': [ + 'view_environments', + 'create_environments', + 'edit_environments', + 'destroy_environments', + 'import_environments', + ], + 'ForemanPuppet::HostClass': [ + 'edit_classes', + ], + 'ForemanPuppet::Puppetclass': [ + 'view_puppetclasses', + 'create_puppetclasses', + 'edit_puppetclasses', + 'destroy_puppetclasses', + 'import_puppetclasses', + ], + 'ForemanPuppet::PuppetclassLookupKey': [ + 'view_external_parameters', + 'create_external_parameters', + 'edit_external_parameters', + 'destroy_external_parameters', + ], 'HttpProxy': [ 'view_http_proxies', 'create_http_proxies', @@ -1089,6 +1105,12 @@ class Colored(Box): 'destroy_locations', 'assign_locations', ], + 'LookupValue': [ + 'edit_lookup_values', + 'create_lookup_values', + 'destroy_lookup_values', + 'view_lookup_values', + ], 'MailNotification': ['view_mail_notifications', 'edit_user_mail_notifications'], 'Medium': ['view_media', 'create_media', 'edit_media', 'destroy_media'], 'Model': ['view_models', 'create_models', 'edit_models', 'destroy_models'], @@ -1120,7 +1142,7 @@ class Colored(Box): 'lock_ptables', ], 'Realm': ['view_realms', 'create_realms', 'edit_realms', 'destroy_realms'], - 'RemoteExecutionFeature': ['edit_remote_execution_features'], + 'RemoteExecutionFeature': ['view_remote_execution_features', 'edit_remote_execution_features'], 'ReportTemplate': [ 'edit_report_templates', 'destroy_report_templates', @@ -1563,6 +1585,7 @@ class Colored(Box): 'rhel6_content': 'Red Hat rhel6 default content', 'rhel7_content': 'Red Hat rhel7 default content', 'rhel8_content': 'Red Hat rhel8 default content', + 'rhel9_content': 'Red Hat rhel9 default content', 'rhel_firefox': 'Red Hat firefox default content', } @@ -1570,7 +1593,8 @@ class Colored(Box): 'c2s_rhel6': 'C2S for Red Hat Enterprise Linux 6', 'dsrhel6': 'DISA STIG for Red Hat Enterprise Linux 6', 'dsrhel7': 'DISA STIG for Red Hat Enterprise Linux 7', - 'dsrhel8': '[DRAFT] DISA STIG for Red Hat Enterprise Linux 8', + 'dsrhel8': 'DISA STIG for Red Hat Enterprise Linux 8', + 'dsrhel9': 'DISA STIG for Red Hat Enterprise Linux 9', 'esp': 'Example Server Profile', 'rhccp': 'Red Hat Corporate Profile for Certified Cloud Providers (RH CCP)', 'firefox': 'Mozilla Firefox STIG', @@ -1650,89 +1674,45 @@ class Colored(Box): 'Viewer', ] -BOOKMARK_ENTITIES = [ - {'name': 'ActivationKey', 'controller': 'katello_activation_keys'}, - {'name': 'Dashboard', 'controller': 'dashboard', 'skip_for_ui': True}, - {'name': 'Audit', 'controller': 'audits', 'skip_for_ui': True}, - {'name': 'Report', 'controller': 'config_reports', 'skip_for_ui': True}, - {'name': 'Task', 'controller': 'foreman_tasks_tasks', 'skip_for_ui': True}, - # TODO Load manifest for the test_positive_end_to_end from the ui/test_bookmarks.py - # {'name': 'Subscriptions', 'controller': 'subscriptions', 'skip_for_ui': True}, - {'name': 'Product', 'controller': 'katello_products'}, - {'name': 'Repository', 'controller': 'katello_repositories', 'skip_for_ui': True}, - {'name': 'ContentCredential', 'controller': 'katello_content_credentials'}, - {'name': 'SyncPlan', 'controller': 'katello_sync_plans'}, - {'name': 'ContentView', 'controller': 'katello_content_views'}, - {'name': 'Errata', 'controller': 'katello_errata', 'skip_for_ui': True}, - {'name': 'Package', 'controller': 'katello_erratum_packages', 'skip_for_ui': True}, - {'name': 'ContainerImageTag', 'controller': 'katello_docker_tags', 'skip_for_ui': True}, - {'name': 'Host', 'controller': 'hosts', 'setup': entities.Host}, - {'name': 'ContentHost', 'controller': 'hosts', 'skip_for_ui': True}, - {'name': 'HostCollection', 'controller': 'katello_host_collections'}, - {'name': 'Architecture', 'controller': 'architectures'}, +BOOKMARK_ENTITIES_SELECTION = [ { - 'name': 'HardwareModel', - 'controller': 'models', - 'setup': entities.Model, - 'skip_for_ui': True, + 'name': 'ActivationKey', + 'controller': 'katello_activation_keys', + 'session_name': 'activationkey', + 'old_ui': True, }, + {'name': 'Errata', 'controller': 'katello_errata', 'session_name': 'errata', 'old_ui': True}, + {'name': 'Host', 'controller': 'hosts', 'setup': entities.Host, 'session_name': 'host_new'}, { - 'name': 'InstallationMedia', - 'controller': 'media', - 'setup': entities.Media, - 'skip_for_ui': True, + 'name': 'UserGroup', + 'controller': 'usergroups', + 'setup': entities.UserGroup, + 'session_name': 'usergroup', }, - {'name': 'OperatingSystem', 'controller': 'operatingsystems'}, { 'name': 'PartitionTable', 'controller': 'ptables', 'setup': entities.PartitionTable, - 'skip_for_ui': False, + 'session_name': 'partitiontable', }, - {'name': 'ProvisioningTemplate', 'controller': 'provisioning_templates'}, { - 'name': 'HostGroup', - 'controller': 'hostgroups', - 'setup': entities.HostGroup, - 'skip_for_ui': True, + 'name': 'Product', + 'controller': 'katello_products', + 'session_name': 'product', + 'old_ui': True, }, { - 'name': 'DiscoveryRule', - 'controller': 'discovery_rules', - 'skip_for_ui': True, - 'setup': entities.DiscoveryRule, + 'name': 'ProvisioningTemplate', + 'controller': 'provisioning_templates', + 'session_name': 'provisioningtemplate', }, - { - 'name': 'GlobalParameter', - 'controller': 'common_parameters', - 'setup': entities.CommonParameter, - 'skip_for_ui': True, - }, - {'name': 'Role', 'controller': 'ansible_roles', 'setup': entities.Role}, - {'name': 'Variables', 'controller': 'ansible_variables', 'skip_for_ui': True}, - {'name': 'SmartProxy', 'controller': 'smart_proxies', 'skip_for_ui': True}, - { - 'name': 'ComputeResource', - 'controller': 'compute_resources', - 'setup': entities.LibvirtComputeResource, - }, - {'name': 'ComputeProfile', 'controller': 'compute_profiles', 'setup': entities.ComputeProfile}, - {'name': 'Subnet', 'controller': 'subnets', 'setup': entities.Subnet}, - {'name': 'Domain', 'controller': 'domains', 'setup': entities.Domain}, - {'name': 'Realm', 'controller': 'realms', 'setup': entities.Realm, 'skip_for_ui': True}, - {'name': 'Location', 'controller': 'locations'}, - {'name': 'Organization', 'controller': 'organizations'}, - {'name': 'User', 'controller': 'users'}, - {'name': 'UserGroup', 'controller': 'usergroups', 'setup': entities.UserGroup}, - {'name': 'Role', 'controller': 'roles'}, - {'name': 'Settings', 'controller': 'settings', 'skip_for_ui': True}, + {'name': 'Repository', 'controller': 'katello_repositories', 'session_name': 'repository'}, ] STRING_TYPES = ['alpha', 'numeric', 'alphanumeric', 'latin1', 'utf8', 'cjk', 'html'] VMWARE_CONSTANTS = { 'folder': 'vm', - 'guest_os': 'Red Hat Enterprise Linux 8 (64 bit)', 'scsicontroller': 'LSI Logic Parallel', 'virtualhw_version': 'Default', 'pool': 'Resources', @@ -1771,10 +1751,15 @@ class Colored(Box): ), } - +# Bugzilla statuses used by Robottelo issue handler. OPEN_STATUSES = ("NEW", "ASSIGNED", "POST", "MODIFIED") CLOSED_STATUSES = ("ON_QA", "VERIFIED", "RELEASE_PENDING", "CLOSED") WONTFIX_RESOLUTIONS = ("WONTFIX", "CANTFIX", "DEFERRED") +# Jira statuses used by Robottelo issue handler. +JIRA_OPEN_STATUSES = ("New", "Backlog", "Refinement", "To Do", "In Progress") +JIRA_ONQA_STATUS = "Review" +JIRA_CLOSED_STATUSES = ("Release Pending", "Closed") +JIRA_WONTFIX_RESOLUTIONS = "Obsolete" GROUP_MEMBERSHIP_MAPPER = { "config": { @@ -2090,6 +2075,7 @@ class Colored(Box): 'option is not present in the /etc/dnf/dnf.conf' ) +EXPIRED_MANIFEST = 'expired-manifest.zip' # Data File Paths class DataFile(Box): @@ -2110,3 +2096,4 @@ class DataFile(Box): PARTITION_SCRIPT_DATA_FILE = DATA_DIR.joinpath(PARTITION_SCRIPT_DATA_FILE) OS_TEMPLATE_DATA_FILE = DATA_DIR.joinpath(OS_TEMPLATE_DATA_FILE) FAKE_3_YUM_REPO_RPMS_ANT = DATA_DIR.joinpath(FAKE_3_YUM_REPO_RPMS[0]) + EXPIRED_MANIFEST_FILE = DATA_DIR.joinpath(EXPIRED_MANIFEST) diff --git a/robottelo/constants/repos.py b/robottelo/constants/repos.py index 9738894a689..9d968a3830b 100644 --- a/robottelo/constants/repos.py +++ b/robottelo/constants/repos.py @@ -12,7 +12,7 @@ CUSTOM_RPM_SHA = 'https://fixtures.pulpproject.org/rpm-with-sha/' CUSTOM_RPM_SHA_512 = 'https://fixtures.pulpproject.org/rpm-with-sha-512/' FAKE_5_YUM_REPO = 'https://rplevka.fedorapeople.org/fakerepo01/' -FAKE_YUM_DRPM_REPO = 'https://fixtures.pulpproject.org/drpm-signed/' +FAKE_YUM_MISSING_REPO = 'https://fixtures.pulpproject.org/missing-repo/' FAKE_YUM_SRPM_REPO = 'https://fixtures.pulpproject.org/srpm-signed/' FAKE_YUM_SRPM_DUPLICATE_REPO = 'https://fixtures.pulpproject.org/srpm-duplicate/' FAKE_YUM_MD5_REPO = 'https://fixtures.pulpproject.org/rpm-with-md5/' @@ -25,6 +25,6 @@ FAKE_0_YUM_REPO_STRING_BASED_VERSIONS = ( 'https://fixtures.pulpproject.org/rpm-string-version-updateinfo/' ) - +FAKE_ZST_REPO = 'https://fixtures.pulpproject.org/rpm-zstd-metadata' ANSIBLE_GALAXY = 'https://galaxy.ansible.com/' ANSIBLE_HUB = 'https://cloud.redhat.com/api/automation-hub/' diff --git a/robottelo/exceptions.py b/robottelo/exceptions.py index 83022dfcd6e..a6100564873 100644 --- a/robottelo/exceptions.py +++ b/robottelo/exceptions.py @@ -75,6 +75,12 @@ class CLIError(Exception): """Indicates that a CLI command could not be run.""" +class CapsuleHostError(Exception): + """Indicates error in capsule configuration etc""" + + pass + + class CLIBaseError(Exception): """Indicates that a CLI command has finished with return code different from zero. diff --git a/robottelo/host_helpers/api_factory.py b/robottelo/host_helpers/api_factory.py index ba830abda2b..1316bca9078 100644 --- a/robottelo/host_helpers/api_factory.py +++ b/robottelo/host_helpers/api_factory.py @@ -51,6 +51,7 @@ def make_http_proxy(self, org, http_proxy_type): password=settings.http_proxy.password, organization=[org.id], ).create() + return None def cv_publish_promote(self, name=None, env_name=None, repo_id=None, org_id=None): """Create, publish and promote CV to selected environment""" @@ -607,10 +608,9 @@ def update_provisioning_template(self, name=None, old=None, new=None): self.temp.template = self.temp.template.replace(old, new, 1) update = self.temp.update(['template']) return new in update.template - elif new in self.temp.template: + if new in self.temp.template: return True - else: - raise ValueError(f'{old} does not exists in template {name}') + raise ValueError(f'{old} does not exists in template {name}') def disable_syncplan(self, sync_plan): """ @@ -694,10 +694,7 @@ def wait_for_errata_applicability_task( task.label == 'Actions::Katello::Applicability::Hosts::BulkGenerate' and 'host_ids' in task.input and host_id in task.input['host_ids'] - ): - task.poll(poll_rate=poll_rate, timeout=poll_timeout) - tasks_finished += 1 - elif ( + ) or ( task.label == 'Actions::Katello::Host::UploadPackageProfile' and 'host' in task.input and host_id == task.input['host']['id'] @@ -766,7 +763,7 @@ def wait_for_syncplan_tasks(self, repo_backend_id=None, timeout=10, repo_name=No if len(req.content) > 2: if req.json()[0].get('state') in ['finished']: return True - elif req.json()[0].get('error'): + if req.json()[0].get('error'): raise AssertionError( f"Pulp task with repo_id {repo_backend_id} error or not found: " f"'{req.json().get('error')}'" diff --git a/robottelo/host_helpers/capsule_mixins.py b/robottelo/host_helpers/capsule_mixins.py index 9e283509696..0e7ffd21065 100644 --- a/robottelo/host_helpers/capsule_mixins.py +++ b/robottelo/host_helpers/capsule_mixins.py @@ -1,6 +1,8 @@ -from datetime import datetime +from datetime import datetime, timedelta import time +from dateutil.parser import parse + from robottelo.constants import PUPPET_CAPSULE_INSTALLER, PUPPET_COMMON_INSTALLER_OPTS from robottelo.logging import logger from robottelo.utils.installer import InstallerCommand @@ -55,32 +57,84 @@ def wait_for_tasks( for task in tasks: task.poll(poll_rate=poll_rate, timeout=poll_timeout, must_succeed=must_succeed) break - else: - time.sleep(search_rate) + time.sleep(search_rate) else: raise AssertionError(f"No task was found using query '{search_query}'") return tasks - def wait_for_sync(self, timeout=600, start_time=None): - """Wait for capsule sync to finish and assert the sync task succeeded""" - # Assert that a task to sync lifecycle environment to the capsule - # is started (or finished already) + def wait_for_sync(self, start_time=None, timeout=600): + """Wait for capsule sync to finish and assert success. + Assert that a task to sync lifecycle environment to the + capsule is started (or finished already), and succeeded. + :raises: ``AssertionError``: If a capsule sync verification fails based on the conditions. + + - Found some active sync task(s) for capsule, or it just finished (recent sync time). + - Any active sync task(s) polled, succeeded, and the capsule last_sync_time is updated. + - last_sync_time after final task is on or newer than start_time. + - The total sync time duration (seconds) is within timeout and not negative. + + :param start_time: (datetime): UTC time to compare against capsule's last_sync_time. + Default: None (current UTC). + :param timeout: (int) maximum seconds for active task(s) and queries to finish. + + :return: + list of polled finished tasks that were in-progress from `active_sync_tasks`. + """ + # Fetch initial capsule sync status + logger.info(f"Waiting for capsule {self.hostname} sync to finish ...") + sync_status = self.nailgun_capsule.content_get_sync(timeout=timeout, synchronous=True) + # Current UTC time for start_time, if not provided if start_time is None: start_time = datetime.utcnow().replace(microsecond=0) - logger.info(f"Waiting for capsule {self.hostname} sync to finish ...") - sync_status = self.nailgun_capsule.content_get_sync() - logger.info(f"Active tasks {sync_status['active_sync_tasks']}") + # 1s margin of safety for rounding + start_time = ( + (start_time - timedelta(seconds=1)) + .replace(microsecond=0) + .strftime('%Y-%m-%d %H:%M:%S UTC') + ) + # Assert presence of recent sync activity: + # one or more ongoing sync tasks for the capsule, + # Or, capsule's last_sync_time is on or after start_time + assert len(sync_status['active_sync_tasks']) or ( + parse(sync_status['last_sync_time']) >= parse(start_time) + ), ( + f"No active or recent sync found for capsule {self.hostname}." + f" `active_sync_tasks` was empty: {sync_status['active_sync_tasks']}," + f" and the `last_sync_time`: {sync_status['last_sync_time']}," + f" was prior to the `start_time`: {start_time}." + ) + sync_tasks = [] + # Poll and verify succeeds, any active sync task from initial status. + logger.info(f"Active tasks: {sync_status['active_sync_tasks']}") + for task in sync_status['active_sync_tasks']: + sync_tasks.append(self.satellite.api.ForemanTask(id=task['id']).poll(timeout=timeout)) + logger.info(f"Active sync task :id {task['id']} succeeded.") + + # Fetch updated capsule status (expect no ongoing sync) + logger.info(f"Querying updated sync status from capsule {self.hostname}.") + updated_status = self.nailgun_capsule.content_get_sync(timeout=timeout, synchronous=True) + # Last sync task end time is the same as capsule's last sync time. + assert parse(updated_status['last_sync_time']) == parse( + updated_status['last_sync_task']['ended_at'] + ), f"`last_sync_time` does not match final task's end time. Capsule: {self.hostname}" + + # Total time taken is not negative (sync prior to start_time), + # and did not exceed timeout. assert ( - len(sync_status['active_sync_tasks']) - or datetime.strptime(sync_status['last_sync_time'], '%Y-%m-%d %H:%M:%S UTC') - >= start_time + timedelta(seconds=0) + <= parse(updated_status['last_sync_time']) - parse(start_time) + <= timedelta(seconds=timeout) + ), ( + f"No recent sync task(s) were found for capsule: {self.hostname}, or task(s) timed out." + f" `last_sync_time`: ({updated_status['last_sync_time']}) was prior to `start_time`: ({start_time})" + f" or exceeded timeout ({timeout}s)." ) + # No failed or active tasks remaining + assert len(updated_status['last_failed_sync_tasks']) == 0 + assert len(updated_status['active_sync_tasks']) == 0 - # Wait till capsule sync finishes and assert the sync task succeeded - for task in sync_status['active_sync_tasks']: - self.satellite.api.ForemanTask(id=task['id']).poll(timeout=timeout) - sync_status = self.nailgun_capsule.content_get_sync() - assert len(sync_status['last_failed_sync_tasks']) == 0 + # return any polled sync tasks, that were initially in-progress + return sync_tasks def get_published_repo_url(self, org, prod, repo, lce=None, cv=None): """Forms url of a repo or CV published on a Satellite or Capsule. @@ -94,5 +148,4 @@ def get_published_repo_url(self, org, prod, repo, lce=None, cv=None): """ if lce and cv: return f'{self.url}/pulp/content/{org}/{lce}/{cv}/custom/{prod}/{repo}/' - else: - return f'{self.url}/pulp/content/{org}/Library/custom/{prod}/{repo}/' + return f'{self.url}/pulp/content/{org}/Library/custom/{prod}/{repo}/' diff --git a/robottelo/host_helpers/cli_factory.py b/robottelo/host_helpers/cli_factory.py index 6debf436c11..13eaf2242ec 100644 --- a/robottelo/host_helpers/cli_factory.py +++ b/robottelo/host_helpers/cli_factory.py @@ -33,7 +33,7 @@ from robottelo.utils.manifest import clone -def create_object(cli_object, options, values=None, credentials=None): +def create_object(cli_object, options, values=None, credentials=None, timeout=None): """ Creates with dictionary of arguments. @@ -52,7 +52,7 @@ def create_object(cli_object, options, values=None, credentials=None): if credentials: cli_object = cli_object.with_user(*credentials) try: - result = cli_object.create(options) + result = cli_object.create(options, timeout) except CLIReturnCodeError as err: # If the object is not created, raise exception, stop the show. raise CLIFactoryError( @@ -140,7 +140,9 @@ def create_object(cli_object, options, values=None, credentials=None): 'sync_plan': { 'description': gen_alpha, 'enabled': 'true', - 'interval': lambda: random.choice(list(constants.SYNC_INTERVAL.values())), + 'interval': lambda: random.choice( + [i for i in constants.SYNC_INTERVAL.values() if i != 'custom cron'] + ), 'name': gen_alpha, 'sync-date': lambda: datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), }, @@ -276,15 +278,13 @@ def __getattr__(self, name): # evaluate functions that provide default values fields = self._evaluate_functions(fields) return partial(create_object, entity_cls, fields) - else: - raise AttributeError(f'unknown factory method name: {name}') + raise AttributeError(f'unknown factory method name: {name}') def _evaluate_function(self, function): """Some functions may require an instance reference""" if 'self' in inspect.signature(function).parameters: return function(self) - else: - return function() + return function() def _evaluate_functions(self, iterable): """Run functions that are used to populate data in lists/dicts""" @@ -296,6 +296,7 @@ def _evaluate_functions(self, iterable): for key, item in iterable.items() if not key.startswith('_') } + return None @lru_cache def _find_entity_class(self, entity_name): @@ -303,6 +304,7 @@ def _find_entity_class(self, entity_name): for name, class_obj in self._satellite.cli.__dict__.items(): if entity_name == name.lower(): return class_obj + return None def make_content_credential(self, options=None): """Creates a content credential. @@ -530,7 +532,7 @@ def make_template(self, options=None): } # Write content to file or random text - if options is not None and 'content' in options.keys(): + if options is not None and 'content' in options: content = options.pop('content') else: content = gen_alphanumeric() @@ -874,32 +876,31 @@ def setup_org_for_a_rh_repo( custom_repo_url = settings.repos.capsule_repo if force_use_cdn or settings.robottelo.cdn or not custom_repo_url: return self._setup_org_for_a_rh_repo(options) - else: - options['url'] = custom_repo_url - result = self.setup_org_for_a_custom_repo(options) - if force_manifest_upload: - with clone() as manifest: - self._satellite.put(manifest.path, manifest.name) - try: - self._satellite.cli.Subscription.upload( - { - 'file': manifest.name, - 'organization-id': result.get('organization-id'), - } - ) - except CLIReturnCodeError as err: - raise CLIFactoryError(f'Failed to upload manifest\n{err.msg}') from err + options['url'] = custom_repo_url + result = self.setup_org_for_a_custom_repo(options) + if force_manifest_upload: + with clone() as manifest: + self._satellite.put(manifest.path, manifest.name) + try: + self._satellite.cli.Subscription.upload( + { + 'file': manifest.name, + 'organization-id': result.get('organization-id'), + } + ) + except CLIReturnCodeError as err: + raise CLIFactoryError(f'Failed to upload manifest\n{err.msg}') from err - # Add default subscription to activation-key, if SCA mode is disabled - if self._satellite.is_sca_mode_enabled(result['organization-id']) is False: - self.activationkey_add_subscription_to_repo( - { - 'activationkey-id': result['activationkey-id'], - 'organization-id': result['organization-id'], - 'subscription': constants.DEFAULT_SUBSCRIPTION_NAME, - } - ) - return result + # Add default subscription to activation-key, if SCA mode is disabled + if self._satellite.is_sca_mode_enabled(result['organization-id']) is False: + self.activationkey_add_subscription_to_repo( + { + 'activationkey-id': result['activationkey-id'], + 'organization-id': result['organization-id'], + 'subscription': constants.DEFAULT_SUBSCRIPTION_NAME, + } + ) + return result @staticmethod def _get_capsule_vm_distro_repos(distro): diff --git a/robottelo/host_helpers/repository_mixins.py b/robottelo/host_helpers/repository_mixins.py index aeb06a0c97e..0ff2bdecd68 100644 --- a/robottelo/host_helpers/repository_mixins.py +++ b/robottelo/host_helpers/repository_mixins.py @@ -388,8 +388,7 @@ def __repr__(self): f'' ) - else: - return f'' + return f'' def create( self, diff --git a/robottelo/host_helpers/satellite_mixins.py b/robottelo/host_helpers/satellite_mixins.py index 27c12c7b3be..d20a70d2d15 100644 --- a/robottelo/host_helpers/satellite_mixins.py +++ b/robottelo/host_helpers/satellite_mixins.py @@ -145,9 +145,10 @@ def upload_manifest(self, org_id, manifest=None, interface='API', timeout=None): :returns: the manifest upload result """ - if not isinstance(manifest, bytes | io.BytesIO): - if not hasattr(manifest, 'content') or manifest.content is None: - manifest = clone() + if not isinstance(manifest, bytes | io.BytesIO) and ( + not hasattr(manifest, 'content') or manifest.content is None + ): + manifest = clone() if timeout is None: # Set the timeout to 1500 seconds to align with the API timeout. timeout = 1500000 @@ -191,8 +192,7 @@ def publish_content_view(self, org, repo_list): repo = repo_list if isinstance(repo_list, list) else [repo_list] content_view = self.api.ContentView(organization=org, repository=repo).create() content_view.publish() - content_view = content_view.read() - return content_view + return content_view.read() def move_pulp_archive(self, org, export_message): """ @@ -208,11 +208,7 @@ def move_pulp_archive(self, org, export_message): # removes everything before export path, # replaces EXPORT_PATH by IMPORT_PATH, # removes metadata filename - import_path = os.path.dirname( - re.sub(rf'.*{PULP_EXPORT_DIR}', PULP_IMPORT_DIR, export_message) - ) - - return import_path + return os.path.dirname(re.sub(rf'.*{PULP_EXPORT_DIR}', PULP_IMPORT_DIR, export_message)) class SystemInfo: @@ -292,10 +288,10 @@ def default_url_on_new_port(self, oldport, newport): post_ncat_procs = self.execute('pgrep ncat').stdout.splitlines() ncat_pid = set(post_ncat_procs).difference(set(pre_ncat_procs)) if not len(ncat_pid): - stderr = channel.get_exit_status()[1] - logger.debug(f'Tunnel failed: {stderr}') + err = channel.get_exit_signal() + logger.debug(f'Tunnel failed: {err}') # Something failed, so raise an exception. - raise CapsuleTunnelError(f'Starting ncat failed: {stderr}') + raise CapsuleTunnelError(f'Starting ncat failed: {err}') forward_url = f'https://{self.hostname}:{newport}' logger.debug(f'Yielding capsule forward port url: {forward_url}') try: diff --git a/robottelo/hosts.py b/robottelo/hosts.py index 2ff72bbe367..f4a6b7fd333 100644 --- a/robottelo/hosts.py +++ b/robottelo/hosts.py @@ -14,6 +14,7 @@ from urllib.parse import urljoin, urlparse, urlunsplit import warnings +import apypie from box import Box from broker import Broker from broker.hosts import Host @@ -76,8 +77,7 @@ def lru_sat_ready_rhel(rhel_ver): 'promtail_config_template_file': 'config_sat.j2', 'workflow': settings.server.deploy_workflows.os, } - sat_ready_rhel = Broker(**deploy_args, host_class=Satellite).checkout() - return sat_ready_rhel + return Broker(**deploy_args, host_class=Satellite).checkout() def get_sat_version(): @@ -171,6 +171,10 @@ class IPAHostError(Exception): pass +class ProxyHostError(Exception): + pass + + class ContentHost(Host, ContentHostMixins): run = Host.execute default_timeout = settings.server.ssh_client.command_timeout @@ -245,8 +249,8 @@ def nailgun_host(self): logger.error(f'Failed to get nailgun host for {self.hostname}: {err}') host = None return host - else: - logger.warning(f'Host {self.hostname} not registered to {self.satellite.hostname}') + logger.warning(f'Host {self.hostname} not registered to {self.satellite.hostname}') + return None @property def subscribed(self): @@ -515,6 +519,7 @@ def enable_repo(self, repo, force=False): downstream_repo = settings.repos.capsule_repo if force or settings.robottelo.cdn or not downstream_repo: return self.execute(f'subscription-manager repos --enable {repo}') + return None def subscription_manager_list_repos(self): return self.execute('subscription-manager repos --list') @@ -1586,12 +1591,14 @@ def check_services(self): for line in result.stdout.splitlines(): if error_msg in line: return line.replace(error_msg, '').strip() + return None def install(self, installer_obj=None, cmd_args=None, cmd_kwargs=None): """General purpose installer""" if not installer_obj: command_opts = {'scenario': self.__class__.__name__.lower()} - command_opts.update(cmd_kwargs) + if cmd_kwargs: + command_opts.update(cmd_kwargs) installer_obj = InstallerCommand(*cmd_args, **command_opts) return self.execute(installer_obj.get_command(), timeout=0) @@ -1599,6 +1606,21 @@ def get_features(self): """Get capsule features""" return requests.get(f'https://{self.hostname}:9090/features', verify=False).text + def enable_capsule_downstream_repos(self): + """Enable CDN repos and capsule downstream repos on Capsule Host""" + # CDN Repos + self.register_to_cdn() + for repo in getattr(constants, f"OHSNAP_RHEL{self.os_version.major}_REPOS"): + result = self.enable_repo(repo, force=True) + if result.status: + raise CapsuleHostError(f'Repo enable at capsule host failed\n{result.stdout}') + # Downstream Capsule specific Repos + self.download_repofile( + product='capsule', + release=settings.capsule.version.release, + snap=settings.capsule.version.snap, + ) + def capsule_setup(self, sat_host=None, capsule_cert_opts=None, **installer_kwargs): """Prepare the host and run the capsule installer""" self._satellite = sat_host or Satellite() @@ -1675,19 +1697,6 @@ def set_rex_script_mode_provider(self, mode='ssh'): if result.status != 0: raise SatelliteHostError(f'Failed to enable pull provider: {result.stdout}') - def run_installer_arg(self, *args, timeout='20m'): - """Run an installer argument on capsule""" - installer_args = list(args) - installer_command = InstallerCommand( - installer_args=installer_args, - ) - result = self.execute( - installer_command.get_command(), - timeout=timeout, - ) - if result.status != 0: - raise SatelliteHostError(f'Failed to execute with argument: {result.stderr}') - def set_mqtt_resend_interval(self, value): """Set the time interval in seconds at which the notification should be re-sent to the mqtt host until the job is picked up or cancelled""" @@ -1730,6 +1739,25 @@ def cli(self): self._cli._configured = True return self._cli + def enable_satellite_or_capsule_module_for_rhel8(self): + """Enable Satellite/Capsule module for RHEL8. + Note: Make sure required repos are enabled before using this. + """ + if self.os_version.major == 8: + assert ( + self.execute( + f'dnf -y module enable {self.product_rpm_name}:el{self.os_version.major}' + ).status + == 0 + ) + + def install_satellite_or_capsule_package(self): + """Install Satellite/Capsule package. Also handles module enablement for RHEL8. + Note: Make sure required repos are enabled before using this. + """ + self.enable_satellite_or_capsule_module_for_rhel8() + assert self.execute(f'dnf -y install {self.product_rpm_name}').status == 0 + class Satellite(Capsule, SatelliteMixins): product_rpm_name = 'satellite' @@ -1743,6 +1771,7 @@ def __init__(self, hostname=None, **kwargs): # create dummy classes for later population self._api = type('api', (), {'_configured': False}) self._cli = type('cli', (), {'_configured': False}) + self._apidoc = None self.record_property = None def _swap_nailgun(self, new_version): @@ -1754,7 +1783,7 @@ def _swap_nailgun(self, new_version): pip_main(['uninstall', '-y', 'nailgun']) pip_main(['install', f'https://github.com/SatelliteQE/nailgun/archive/{new_version}.zip']) self._api = type('api', (), {'_configured': False}) - to_clear = [k for k in sys.modules.keys() if 'nailgun' in k] + to_clear = [k for k in sys.modules if 'nailgun' in k] [sys.modules.pop(k) for k in to_clear] @property @@ -1796,6 +1825,19 @@ class DecClass(cls): self._api._configured = True return self._api + @property + def apidoc(self): + """Provide Satellite's apidoc via apypie""" + if not self._apidoc: + self._apidoc = apypie.Api( + uri=self.url, + username=settings.server.admin_username, + password=settings.server.admin_password, + api_version=2, + verify_ssl=settings.server.verify_ca, + ).apidoc + return self._apidoc + @property def cli(self): """Import all robottelo cli entities and wrap them under self.cli""" @@ -1828,9 +1870,27 @@ def cli(self): @contextmanager def omit_credentials(self): - self.omitting_credentials = True + change = not self.omitting_credentials # if not already set to omit + if change: + self.omitting_credentials = True + # if CLI is already created + if self._cli._configured: + for name, obj in self._cli.__dict__.items(): + with contextlib.suppress( + AttributeError + ): # not everything has an mro method, we don't care about them + if Base in obj.mro(): + getattr(self._cli, name).omitting_credentials = True yield - self.omitting_credentials = False + if change: + self.omitting_credentials = False + if self._cli._configured: + for name, obj in self._cli.__dict__.items(): + with contextlib.suppress( + AttributeError + ): # not everything has an mro method, we don't care about them + if Base in obj.mro(): + getattr(self._cli, name).omitting_credentials = False @contextmanager def ui_session(self, testname=None, user=None, password=None, url=None, login=True): @@ -1844,6 +1904,7 @@ def get_caller(): for frame in inspect.stack(): if frame.function.startswith('test_'): return frame.function + return None try: ui_session = Session( @@ -2525,3 +2586,42 @@ def remove_user_from_usergroup(self, member_username, member_group): ) if result.status != 0: raise IPAHostError('Failed to remove the user from user group') + + +class ProxyHost(Host): + """Class representing HTTP Proxy host""" + + def __init__(self, url, **kwargs): + self._conf_dir = '/etc/squid/' + self._access_log = '/var/log/squid/access.log' + kwargs['hostname'] = urlparse(url).hostname + super().__init__(**kwargs) + + def add_user(self, name, passwd): + """Adds new user to the HTTP Proxy""" + res = self.execute(f"htpasswd -b {self._conf_dir}passwd {name} '{passwd}'") + assert res.status == 0, f'User addition failed on the proxy side: {res.stderr}' + return res + + def remove_user(self, name): + """Removes a user from HTTP Proxy""" + res = self.execute(f'htpasswd -D {self._conf_dir}passwd {name}') + assert res.status == 0, f'User deletion failed on the proxy side: {res.stderr}' + return res + + def get_log(self, which=None, tail=None, grep=None): + """Returns log content from the HTTP Proxy instance + + :param which: Which log file should be read. Defaults to access.log. + :param tail: Use when only the tail of a long log file is needed. + :param grep: Grep for some expression. + :return: Log content found or None + """ + log_file = which or self._access_log + cmd = f'tail -n {tail} {log_file}' if tail else f'cat {log_file}' + if grep: + cmd = f'{cmd} | grep "{grep}"' + res = self.execute(cmd) + if res.status != 0: + raise ProxyHostError(f'Proxy log read failed: {res.stderr}') + return None if res.stdout == '' else res.stdout diff --git a/robottelo/ssh.py b/robottelo/ssh.py index c82a7ebc071..8b72bed3497 100644 --- a/robottelo/ssh.py +++ b/robottelo/ssh.py @@ -16,13 +16,12 @@ def get_client( from robottelo.config import settings from robottelo.hosts import ContentHost - client = ContentHost( + return ContentHost( hostname=hostname or settings.server.hostname, username=username or settings.server.ssh_username, password=password or settings.server.ssh_password, port=port or settings.server.ssh_client.port, ) - return client def command( diff --git a/robottelo/utils/datafactory.py b/robottelo/utils/datafactory.py index 7de716e1046..1834e0f87b0 100644 --- a/robottelo/utils/datafactory.py +++ b/robottelo/utils/datafactory.py @@ -64,11 +64,10 @@ def parametrized(data): 'ids': list(data.keys()), 'argvalues': list(data.values()), } - else: - return { - 'ids': [str(i) for i in range(len(data))], - 'argvalues': list(data), - } + return { + 'ids': [str(i) for i in range(len(data))], + 'argvalues': list(data), + } @filtered_datapoint @@ -196,14 +195,13 @@ def valid_domain_names(interface=None, length=None): length = random.randint(1, max_len) if length > max_len: raise ValueError(f'length is too large, max: {max_len}') - names = { + return { 'alphanumeric': DOMAIN % gen_string('alphanumeric', length), 'alpha': DOMAIN % gen_string('alpha', length), 'numeric': DOMAIN % gen_string('numeric', length), 'latin1': DOMAIN % gen_string('latin1', length), 'utf8': DOMAIN % gen_utf8(length), } - return names @filtered_datapoint @@ -243,8 +241,8 @@ def invalid_values_list(interface=None): raise InvalidArgumentError('Valid interface values are api, cli, ui only') if interface == 'ui': return ['', ' '] + invalid_names_list() - else: # interface = api or cli or None - return ['', ' ', '\t'] + invalid_names_list() + # else: interface = api or cli or None + return ['', ' ', '\t'] + invalid_names_list() @filtered_datapoint @@ -274,7 +272,7 @@ def valid_data_list(interface=None): @filtered_datapoint def valid_docker_repository_names(): """Generates a list of valid names for Docker repository.""" - names = [ + return [ gen_string('alphanumeric', random.randint(1, 255)), gen_string('alpha', random.randint(1, 255)), gen_string('cjk', random.randint(1, 85)), @@ -283,7 +281,6 @@ def valid_docker_repository_names(): gen_string('utf8', random.randint(1, 85)), gen_string('html', random.randint(1, 85)), ] - return names @filtered_datapoint @@ -367,6 +364,24 @@ def valid_hostgroups_list(): ] +@filtered_datapoint +def valid_hostgroups_list_short(): + """Generates a list of valid host group names. Shorter so they can be nested. + + :return: Returns the valid host group names list + """ + return [ + gen_string('alphanumeric', random.randint(1, 15)), + gen_string('alpha', random.randint(1, 15)), + gen_string('cjk', random.randint(1, 15)), + gen_string('latin1', random.randint(1, 15)), + gen_string('numeric', random.randint(1, 15)), + gen_string('utf8', random.randint(1, 15)), + gen_string('html', random.randint(1, 15)), + gen_string('alphanumeric', random.randint(1, 15)), + ] + + @filtered_datapoint def valid_labels_list(): """Generates a list of valid labels.""" @@ -512,8 +527,7 @@ def valid_http_credentials(url_encoded=False): } for cred in credentials ] - else: - return credentials + return credentials def invalid_http_credentials(url_encoded=False): @@ -535,8 +549,7 @@ def invalid_http_credentials(url_encoded=False): } for cred in credentials ] - else: - return credentials + return credentials @filtered_datapoint diff --git a/robottelo/utils/decorators/func_locker.py b/robottelo/utils/decorators/func_locker.py index 4d82c3add28..08f4073c614 100644 --- a/robottelo/utils/decorators/func_locker.py +++ b/robottelo/utils/decorators/func_locker.py @@ -112,10 +112,7 @@ def _get_scope_path(scope, scope_kwargs=None, scope_context=None, create=True): scope_path_list = [_get_temp_lock_function_dir(create=create)] if scope: - if callable(scope): - scope_dir_name = scope(**scope_kwargs) - else: - scope_dir_name = scope + scope_dir_name = scope(**scope_kwargs) if callable(scope) else scope if scope_dir_name: scope_path_list.append(scope_dir_name) if scope_context: @@ -168,8 +165,8 @@ def _check_deadlock(lock_file_path, process_id): """ if os.path.exists(lock_file_path): try: - lock_file_handler = open(lock_file_path) - lock_file_content = lock_file_handler.read() + with open(lock_file_path) as lock_file_handler: + lock_file_content = lock_file_handler.read() except OSError as exp: # do nothing, but anyway log the exception logger.exception(exp) @@ -265,8 +262,7 @@ def wait_function(func): if function: return main_wrapper(function) - else: - return wait_function + return wait_function @contextmanager diff --git a/robottelo/utils/decorators/func_shared/shared.py b/robottelo/utils/decorators/func_shared/shared.py index 072ebc3fb47..73961df50b5 100644 --- a/robottelo/utils/decorators/func_shared/shared.py +++ b/robottelo/utils/decorators/func_shared/shared.py @@ -565,5 +565,4 @@ def wait_function(func): if function_: return main_wrapper(function_) - else: - return wait_function + return wait_function diff --git a/robottelo/utils/io/__init__.py b/robottelo/utils/io/__init__.py index 33a4381fd4c..ac23b5e83b5 100644 --- a/robottelo/utils/io/__init__.py +++ b/robottelo/utils/io/__init__.py @@ -82,6 +82,7 @@ def get_remote_report_checksum(satellite, org_id): continue checksum, _ = result.stdout.split(maxsplit=1) return checksum + return None def get_report_data(report_path): diff --git a/robottelo/utils/issue_handlers/README.md b/robottelo/utils/issue_handlers/README.md index 1ef130583d0..8661a4965e5 100644 --- a/robottelo/utils/issue_handlers/README.md +++ b/robottelo/utils/issue_handlers/README.md @@ -13,7 +13,7 @@ Issue handler should expose 3 functions. ### `is_open_(issue, data=None)` -e.g: `is_open_bz, is_open_gh, is_open_jr` for Bugzilla, Github and Jira. +e.g: `is_open_bz, is_open_gh, is_open_jira` for Bugzilla, Github and Jira. This function is dispatched from `robottelo.helpers.is_open` that is also used to check for status in the `pytest.mark.skip_if_open` marker. @@ -78,10 +78,10 @@ Example of `collected_data`: ## Issue handlers implemented - `.bugzilla.py`: BZ:123456 +- `.jira.py`: SAT-22761 ## Issue handlers to be implemented - `.github.py`: GH:satelliteqe/robottelo#123 - `.gitlab.py`: GL:path/to/repo#123 -- `.jira.py`: JR:SATQE-4561 - `.redmine.py`: RM:pulp.plan.io#5580 diff --git a/robottelo/utils/issue_handlers/__init__.py b/robottelo/utils/issue_handlers/__init__.py index 4789bbc8a74..803b5800080 100644 --- a/robottelo/utils/issue_handlers/__init__.py +++ b/robottelo/utils/issue_handlers/__init__.py @@ -1,8 +1,10 @@ +import re + # Methods related to issue handlers in general -from robottelo.utils.issue_handlers import bugzilla +from robottelo.utils.issue_handlers import bugzilla, jira -handler_methods = {'BZ': bugzilla.is_open_bz} -SUPPORTED_HANDLERS = tuple(f"{handler}:" for handler in handler_methods.keys()) +handler_methods = {'BZ': bugzilla.is_open_bz, 'SAT': jira.is_open_jira} +SUPPORTED_HANDLERS = tuple(f"{handler}" for handler in handler_methods) def add_workaround(data, matches, usage, validation=(lambda *a, **k: True), **kwargs): @@ -16,11 +18,13 @@ def add_workaround(data, matches, usage, validation=(lambda *a, **k: True), **kw def should_deselect(issue, data=None): """Check if test should be deselected based on marked issue.""" # Handlers can be extended to support different issue trackers. - handlers = {'BZ': bugzilla.should_deselect_bz} - supported_handlers = tuple(f"{handler}:" for handler in handlers.keys()) + handlers = {'BZ': bugzilla.should_deselect_bz, 'SAT': jira.should_deselect_jira} + supported_handlers = tuple(f"{handler}" for handler in handlers) if str(issue).startswith(supported_handlers): - handler_code = str(issue).partition(":")[0] + res = re.split(':|-', issue) + handler_code = res[0] return handlers[handler_code.strip()](issue.strip(), data) + return None def is_open(issue, data=None): @@ -28,7 +32,7 @@ def is_open(issue, data=None): Issue must be prefixed by its handler e.g: - Bugzilla: BZ:123456 + Bugzilla: BZ:123456, Jira: SAT-12345 Arguments: issue {str} -- A string containing handler + number e.g: BZ:123465 @@ -36,11 +40,12 @@ def is_open(issue, data=None): """ # Handlers can be extended to support different issue trackers. if str(issue).startswith(SUPPORTED_HANDLERS): - handler_code = str(issue).partition(":")[0] + res = re.split(':|-', issue) + handler_code = res[0] else: # EAFP raise AttributeError( "is_open argument must be a string starting with a handler code " - "e.g: 'BZ:123456'" + "e.g: 'BZ:123456' for Bugzilla and 'SAT-12345' for Jira." f"supported handlers are: {SUPPORTED_HANDLERS}" ) return handler_methods[handler_code.strip()](issue.strip(), data) diff --git a/robottelo/utils/issue_handlers/bugzilla.py b/robottelo/utils/issue_handlers/bugzilla.py index 20836a3660d..dd1c35da2ab 100644 --- a/robottelo/utils/issue_handlers/bugzilla.py +++ b/robottelo/utils/issue_handlers/bugzilla.py @@ -137,7 +137,7 @@ def collect_data_bz(collected_data, cached_data): # pragma: no cover def collect_dupes(bz, collected_data, cached_data=None): # pragma: no cover - """Recursivelly find for duplicates""" + """Recursively find for duplicates""" cached_data = cached_data or {} if bz.get('resolution') == 'DUPLICATE': # Collect duplicates @@ -180,15 +180,15 @@ def collect_clones(bz, collected_data, cached_data=None): # pragma: no cover @retry( - stop=stop_after_attempt(4), # Retry 3 times before raising - wait=wait_fixed(20), # Wait seconds between retries + stop=stop_after_attempt(4), + wait=wait_fixed(20), ) def get_data_bz(bz_numbers, cached_data=None): # pragma: no cover """Get a list of marked BZ data and query Bugzilla REST API. Arguments: bz_numbers {list of str} -- ['123456', ...] - cached_data + cached_data {dict} -- Cached data previous loaded from API Returns: [list of dicts] -- [{'id':..., 'status':..., 'resolution': ...}] diff --git a/robottelo/utils/issue_handlers/jira.py b/robottelo/utils/issue_handlers/jira.py new file mode 100644 index 00000000000..dfeb1c966c4 --- /dev/null +++ b/robottelo/utils/issue_handlers/jira.py @@ -0,0 +1,274 @@ +from collections import defaultdict +import re + +from packaging.version import Version +import pytest +import requests +from tenacity import retry, stop_after_attempt, wait_fixed + +from robottelo.config import settings +from robottelo.constants import ( + JIRA_CLOSED_STATUSES, + JIRA_ONQA_STATUS, + JIRA_OPEN_STATUSES, + JIRA_WONTFIX_RESOLUTIONS, +) +from robottelo.hosts import get_sat_version +from robottelo.logging import logger + +# match any version as in `sat-6.14.x` or `sat-6.13.0` or `6.13.9` +# The .version group being a `d.d` string that can be casted to Version() +VERSION_RE = re.compile(r'(?:sat-)*?(?P\d\.\d)\.\w*') + + +def is_open_jira(issue, data=None): + """Check if specific Jira is open consulting a cached `data` dict or + calling Jira REST API. + + Arguments: + issue {str} -- The Jira reference e.g: SAT-20548 + data {dict} -- Issue data indexed by : or None + """ + jira = try_from_cache(issue, data) + if jira.get("is_open") is not None: # issue has been already processed + return jira["is_open"] + + jira = follow_duplicates(jira) + status = jira.get('status', '') + resolution = jira.get('resolution', '') + + # Jira is explicitly in OPEN status + if status in JIRA_OPEN_STATUSES: + return True + + # Jira is Closed/Obsolete so considered not fixed yet, Jira is open + if status in JIRA_CLOSED_STATUSES and resolution in JIRA_WONTFIX_RESOLUTIONS: + return True + + # Jira is Closed with a resolution in (Done, Done-Errata, ...) + # server.version is higher or equal than Jira fixVersion + # Consider fixed, Jira is not open + fix_version = jira.get('fixVersions') + if fix_version: + return get_sat_version() < Version(min(fix_version)) + return status not in JIRA_CLOSED_STATUSES and status != JIRA_ONQA_STATUS + + +def are_all_jira_open(issues, data=None): + """Check if all Jira is open consulting a cached `data` dict or + calling Jira REST API. + + Arguments: + issues {list} -- The Jira reference e.g: ['SAT-20548', 'SAT-20548'] + data {dict} -- Issue data indexed by : or None + """ + return all(is_open_jira(issue, data) for issue in issues) + + +def are_any_jira_open(issues, data=None): + """Check if any of the Jira is open consulting a cached `data` dict or + calling Jira REST API. + + Arguments: + issues {list} -- The Jira reference e.g: ['SAT-20548', 'SAT-20548'] + data {dict} -- Issue data indexed by : or None + """ + return any(is_open_jira(issue, data) for issue in issues) + + +def should_deselect_jira(issue, data=None): + """Check if test should be deselected based on marked issue. + + 1. Resolution "Obsolete" should deselect + + Arguments: + issue {str} -- The Jira reference e.g: SAT-12345 + data {dict} -- Issue data indexed by : or None + """ + + jira = try_from_cache(issue, data) + if jira.get("is_deselected") is not None: # issue has been already processed + return jira["is_deselected"] + + jira = follow_duplicates(jira) + + return ( + jira.get('status') in JIRA_CLOSED_STATUSES + and jira.get('resolution') in JIRA_WONTFIX_RESOLUTIONS + ) + + +def follow_duplicates(jira): + """recursively load the duplicate data""" + if jira.get('dupe_data'): + jira = follow_duplicates(jira['dupe_data']) + return jira + + +def try_from_cache(issue, data=None): + """Try to fetch issue from given data cache or previous loaded on pytest. + + Arguments: + issue {str} -- The Jira reference e.g: SAT-12345 + data {dict} -- Issue data indexed by : or None + """ + try: + # issue must be passed in `data` argument or already fetched in pytest + if not data and not len(pytest.issue_data[issue]['data']): + raise ValueError + return data or pytest.issue_data[issue]['data'] + except (KeyError, AttributeError, ValueError): # pragma: no cover + # If not then call Jira API again + return get_single_jira(str(issue)) + + +def collect_data_jira(collected_data, cached_data): # pragma: no cover + """Collect data from Jira API and aggregate in a dictionary. + + Arguments: + collected_data {dict} -- dict with Jira issues collected by pytest + cached_data {dict} -- Cached data previous loaded from API + """ + jira_data = ( + get_data_jira( + [item for item in collected_data if item.startswith('SAT-')], + cached_data=cached_data, + ) + or [] + ) + for data in jira_data: + # If Jira is CLOSED/DUPLICATE collect the duplicate + collect_dupes(data, collected_data, cached_data=cached_data) + + jira_key = f"{data['key']}" + data["is_open"] = is_open_jira(jira_key, data) + collected_data[jira_key]['data'] = data + + +def collect_dupes(jira, collected_data, cached_data=None): # pragma: no cover + """Recursively find for duplicates""" + cached_data = cached_data or {} + if jira.get('resolution') == 'Duplicate': + # Collect duplicates + jira['dupe_data'] = get_single_jira(jira.get('dupe_of'), cached_data=cached_data) + dupe_key = f"{jira['dupe_of']}" + # Store Duplicate also in the main collection for caching + if dupe_key not in collected_data: + collected_data[dupe_key]['data'] = jira['dupe_data'] + collected_data[dupe_key]['is_dupe'] = True + collect_dupes(jira['dupe_data'], collected_data, cached_data) + + +# --- API Calls --- + +# cannot use lru_cache in functions that has unhashable args +CACHED_RESPONSES = defaultdict(dict) + + +@retry( + stop=stop_after_attempt(4), # Retry 3 times before raising + wait=wait_fixed(20), # Wait seconds between retries +) +def get_data_jira(jira_numbers, cached_data=None): # pragma: no cover + """Get a list of marked Jira data and query Jira REST API. + + Arguments: + jira_numbers {list of str} -- ['SAT-12345', ...] + cached_data {dict} -- Cached data previous loaded from API + + Returns: + [list of dicts] -- [{'id':..., 'status':..., 'resolution': ...}] + """ + if not jira_numbers: + return [] + + cached_by_call = CACHED_RESPONSES['get_data'].get(str(sorted(jira_numbers))) + if cached_by_call: + return cached_by_call + + if cached_data: + logger.debug(f"Using cached data for {set(jira_numbers)}") + if not all([f'{number}' in cached_data for number in jira_numbers]): + logger.debug("There are Jira's out of cache.") + return [item['data'] for _, item in cached_data.items() if 'data' in item] + + # Ensure API key is set + if not settings.jira.api_key: + logger.warning( + "Config file is missing jira api_key " + "so all tests with skip_if_open mark is skipped. " + "Provide api_key or a jira_cache.json." + ) + # Provide default data for collected Jira's. + return [get_default_jira(number) for number in jira_numbers] + + # No cached data so Call Jira API + logger.debug(f"Calling Jira API for {set(jira_numbers)}") + jira_fields = [ + "key", + "summary", + "status", + "resolution", + "fixVersions", + ] + # Following fields are dynamically calculated/loaded + for field in ('is_open', 'version'): + assert field not in jira_fields + + # Generate jql + jql = ' OR '.join([f"id = {id}" for id in jira_numbers]) + + response = requests.get( + f"{settings.jira.url}/rest/api/latest/search/", + params={ + "jql": jql, + "fields": ",".join(jira_fields), + }, + headers={"Authorization": f"Bearer {settings.jira.api_key}"}, + ) + response.raise_for_status() + data = response.json().get('issues') + # Clean the data, only keep the required info. + data = [ + { + 'key': issue['key'], + 'summary': issue['fields']['summary'], + 'status': issue['fields']['status']['name'], + 'resolution': issue['fields']['resolution']['name'] + if issue['fields']['resolution'] + else '', + 'fixVersions': [ver['name'] for ver in issue['fields']['fixVersions']] + if issue['fields']['fixVersions'] + else [], + } + for issue in data + if issue is not None + ] + CACHED_RESPONSES['get_data'][str(sorted(jira_numbers))] = data + return data + + +def get_single_jira(number, cached_data=None): # pragma: no cover + """Call Jira API to get a single Jira data and cache it""" + cached_data = cached_data or {} + jira_data = CACHED_RESPONSES['get_single'].get(number) + if not jira_data: + try: + jira_data = cached_data[f"{number}"]['data'] + except (KeyError, TypeError): + jira_data = get_data_jira([str(number)], cached_data) + jira_data = jira_data and jira_data[0] + CACHED_RESPONSES['get_single'][number] = jira_data + return jira_data or get_default_jira(number) + + +def get_default_jira(number): # pragma: no cover + """This is the default Jira data when it is not possible to reach Jira api""" + return { + "key": number, + "is_open": True, + "is_deselected": False, + "status": "", + "resolution": "", + "error": "missing jira api_key", + } diff --git a/robottelo/utils/ohsnap.py b/robottelo/utils/ohsnap.py index 89eb6a97e22..96241a759b6 100644 --- a/robottelo/utils/ohsnap.py +++ b/robottelo/utils/ohsnap.py @@ -121,7 +121,8 @@ def ohsnap_snap_rpms(ohsnap, sat_version, snap_version, os_major, is_all=True): rpm_repos = [f'satellite {sat_xy}', f'maintenance {sat_xy}'] if res.status_code == 200: for repo_data in res.json(): - if repo_data['rhel'] == os_major: - if any(repo in repo_data['repository'].lower() for repo in rpm_repos): - rpms += repo_data['rpms'] + if repo_data['rhel'] == os_major and any( + repo in repo_data['repository'].lower() for repo in rpm_repos + ): + rpms += repo_data['rpms'] return rpms diff --git a/robottelo/utils/report_portal/portal.py b/robottelo/utils/report_portal/portal.py index 3d44ac6c691..c7e0df5d54e 100644 --- a/robottelo/utils/report_portal/portal.py +++ b/robottelo/utils/report_portal/portal.py @@ -101,10 +101,9 @@ def get_launches( resp.raise_for_status() # this should further filter out unfinished launches as RP API currently doesn't # support usage of the same filter type multiple times (filter.ne.status) - launches = [ + return [ launch for launch in resp.json()['content'] if launch['status'] not in ['INTERRUPTED'] ] - return launches @retry( stop=stop_after_attempt(6), diff --git a/robottelo/utils/ssh.py b/robottelo/utils/ssh.py index c82a7ebc071..8b72bed3497 100644 --- a/robottelo/utils/ssh.py +++ b/robottelo/utils/ssh.py @@ -16,13 +16,12 @@ def get_client( from robottelo.config import settings from robottelo.hosts import ContentHost - client = ContentHost( + return ContentHost( hostname=hostname or settings.server.hostname, username=username or settings.server.ssh_username, password=password or settings.server.ssh_password, port=port or settings.server.ssh_client.port, ) - return client def command( diff --git a/robottelo/utils/vault.py b/robottelo/utils/vault.py index d447331ac15..97f95755bbd 100644 --- a/robottelo/utils/vault.py +++ b/robottelo/utils/vault.py @@ -10,7 +10,6 @@ class Vault: - HELP_TEXT = ( "Vault CLI in not installed in your system, " "refer link https://learn.hashicorp.com/tutorials/vault/getting-started-install to " @@ -41,12 +40,15 @@ def export_vault_addr(self): os.environ['VAULT_ADDR'] = vaulturl # Dynaconf Vault Env Vars - if self.vault_enabled and self.vault_enabled in ['True', 'true']: - if 'localhost:8200' in vaulturl: - raise InvalidVaultURLForOIDC( - f"{vaulturl} doesn't support OIDC login," - "please change url to corp vault in env file!" - ) + if ( + self.vault_enabled + and self.vault_enabled in ['True', 'true'] + and 'localhost:8200' in vaulturl + ): + raise InvalidVaultURLForOIDC( + f"{vaulturl} doesn't support OIDC login," + "please change url to corp vault in env file!" + ) def exec_vault_command(self, command: str, **kwargs): """A wrapper to execute the vault CLI commands @@ -74,31 +76,29 @@ def login(self, **kwargs): self.vault_enabled and self.vault_enabled in ['True', 'true'] and 'VAULT_SECRET_ID_FOR_DYNACONF' not in os.environ + and self.status(**kwargs).returncode != 0 ): - if self.status(**kwargs).returncode != 0: - logger.info( - "Warning! The browser is about to open for vault OIDC login, " - "close the tab once the sign-in is done!" - ) - if ( - self.exec_vault_command(command="vault login -method=oidc", **kwargs).returncode - == 0 - ): - self.exec_vault_command(command="vault token renew -i 10h", **kwargs) - logger.info("Success! Vault OIDC Logged-In and extended for 10 hours!") - # Fetching tokens - token = self.exec_vault_command("vault token lookup --format json").stdout - token = json.loads(str(token.decode('UTF-8')))['data']['id'] - # Setting new token in env file - _envdata = re.sub( - '.*VAULT_TOKEN_FOR_DYNACONF=.*', - f"VAULT_TOKEN_FOR_DYNACONF={token}", - self.envdata, - ) - self.env_path.write_text(_envdata) - logger.info( - "Success! New OIDC token added to .env file to access secrets from vault!" - ) + logger.info( + "Warning! The browser is about to open for vault OIDC login, " + "close the tab once the sign-in is done!" + ) + if ( + self.exec_vault_command(command="vault login -method=oidc", **kwargs).returncode + == 0 + ): + self.exec_vault_command(command="vault token renew -i 10h", **kwargs) + logger.info("Success! Vault OIDC Logged-In and extended for 10 hours!") + # Fetching tokens + token = self.exec_vault_command("vault token lookup --format json").stdout + token = json.loads(str(token.decode('UTF-8')))['data']['id'] + # Setting new token in env file + _envdata = re.sub( + '.*VAULT_TOKEN_FOR_DYNACONF=.*', + f"VAULT_TOKEN_FOR_DYNACONF={token}", + self.envdata, + ) + self.env_path.write_text(_envdata) + logger.info("Success! New OIDC token added to .env file to access secrets from vault!") def logout(self): # Teardown - Setting dymmy token in env file diff --git a/robottelo/utils/virtwho.py b/robottelo/utils/virtwho.py index 5e0f3a4657a..b1cac0012ca 100644 --- a/robottelo/utils/virtwho.py +++ b/robottelo/utils/virtwho.py @@ -47,16 +47,15 @@ def get_system(system_type): 'password': getattr(settings.virtwho, system_type).guest_password, 'port': getattr(settings.virtwho, system_type).guest_port, } - elif system_type == 'satellite': + if system_type == 'satellite': return { 'hostname': settings.server.hostname, 'username': settings.server.ssh_username, 'password': settings.server.ssh_password, } - else: - raise VirtWhoError( - f'"{system_type}" system type is not supported. Please use one of {system_type_list}' - ) + raise VirtWhoError( + f'"{system_type}" system type is not supported. Please use one of {system_type_list}' + ) def get_guest_info(hypervisor_type): @@ -115,8 +114,7 @@ def register_system(system, activation_key=None, org='Default_Organization', env ret, stdout = runcmd(cmd, system) if ret == 0 or "system has been registered" in stdout: return True - else: - raise VirtWhoError(f'Failed to register system: {system}') + raise VirtWhoError(f'Failed to register system: {system}') def virtwho_cleanup(): @@ -150,10 +148,9 @@ def get_virtwho_status(): return 'logerror' if any(key in stdout for key in running_stauts): return 'running' - elif any(key in stdout for key in stopped_status): + if any(key in stdout for key in stopped_status): return 'stopped' - else: - return 'undefined' + return 'undefined' def get_configure_id(name): @@ -164,8 +161,7 @@ def get_configure_id(name): config = VirtWhoConfig.info({'name': name}) if 'id' in config['general-information']: return config['general-information']['id'] - else: - raise VirtWhoError(f"No configure id found for {name}") + raise VirtWhoError(f"No configure id found for {name}") def get_configure_command(config_id, org=DEFAULT_ORG): @@ -198,10 +194,8 @@ def get_configure_option(option, filename): cmd = f"grep -v '^#' {filename} | grep ^{option}" ret, stdout = runcmd(cmd) if ret == 0 and option in stdout: - value = stdout.split('=')[1].strip() - return value - else: - raise VirtWhoError(f"option {option} is not exist or not be enabled in {filename}") + return stdout.split('=')[1].strip() + raise VirtWhoError(f"option {option} is not exist or not be enabled in {filename}") def get_rhsm_log(): @@ -216,7 +210,7 @@ def check_message_in_rhsm_log(message): """Check the message exist in /var/log/rhsm/rhsm.log""" wait_for( lambda: 'Host-to-guest mapping being sent to' in get_rhsm_log(), - timeout=10, + timeout=20, delay=2, ) logs = get_rhsm_log() @@ -236,7 +230,7 @@ def _get_hypervisor_mapping(hypervisor_type): """ wait_for( lambda: 'Host-to-guest mapping being sent to' in get_rhsm_log(), - timeout=10, + timeout=20, delay=2, ) logs = get_rhsm_log() @@ -264,8 +258,7 @@ def _get_hypervisor_mapping(hypervisor_type): break if hypervisor_name: return hypervisor_name, guest_name - else: - raise VirtWhoError(f"Failed to get the hypervisor_name for guest {guest_name}") + raise VirtWhoError(f"Failed to get the hypervisor_name for guest {guest_name}") def get_hypervisor_ahv_mapping(hypervisor_type): @@ -348,6 +341,7 @@ def deploy_configure_by_command(command, hypervisor_type, debug=False, org='Defa raise VirtWhoError(f"Failed to deploy configure by {command}") if debug: return deploy_validation(hypervisor_type) + return None def deploy_configure_by_script( @@ -371,6 +365,7 @@ def deploy_configure_by_script( raise VirtWhoError(f"Failed to deploy configure by {script_filename}") if debug: return deploy_validation(hypervisor_type) + return None def deploy_configure_by_command_check(command): @@ -389,8 +384,7 @@ def deploy_configure_by_command_check(command): else: if ret != 0 or 'Finished successfully' not in stdout: raise VirtWhoError(f"Failed to deploy configure by {command}") - else: - return 'Finished successfully' + return 'Finished successfully' def restart_virtwho_service(): @@ -469,8 +463,7 @@ def hypervisor_json_create(hypervisors, guests): name = str(uuid.uuid4()) hypervisor = {"guestIds": guest_list, "name": name, "hypervisorId": {"hypervisorId": name}} hypervisors_list.append(hypervisor) - mapping = {"hypervisors": hypervisors_list} - return mapping + return {"hypervisors": hypervisors_list} def create_fake_hypervisor_content(org_label, hypervisors, guests): @@ -537,7 +530,8 @@ def get_configure_command_option(deploy_type, args, org=DEFAULT_ORG): username, password = Base._get_username_password() if deploy_type == 'location-id': return f"hammer -u {username} -p {password} virt-who-config deploy --id {args['id']} --location-id '{args['location-id']}' " - elif deploy_type == 'organization-title': + if deploy_type == 'organization-title': return f"hammer -u {username} -p {password} virt-who-config deploy --id {args['id']} --organization-title '{args['organization-title']}' " - elif deploy_type == 'name': + if deploy_type == 'name': return f"hammer -u {username} -p {password} virt-who-config deploy --name {args['name']} --organization '{org}' " + return None diff --git a/scripts/config_helpers.py b/scripts/config_helpers.py index feb37c9bd62..cf422588b05 100644 --- a/scripts/config_helpers.py +++ b/scripts/config_helpers.py @@ -35,7 +35,7 @@ def merge_nested_dictionaries(original, new, overwrite=False): user_choice = click.prompt(choice_prompt, type=int, default=1, show_default=True) if user_choice == 1: continue - elif user_choice == 2: + if user_choice == 2: original[key] = value elif user_choice == 3 and isinstance(value, list): original[key] = original[key] + value @@ -110,7 +110,7 @@ def merge(from_, strategy): user_choice = click.prompt(choice_prompt, type=int, default=1, show_default=True) if user_choice == 1: continue - elif user_choice == 2: + if user_choice == 2: logger.warning(f"Overwriting {real_name} with {file}") real_name.unlink() real_name.write_text(file.read_text()) diff --git a/scripts/customer_scenarios.py b/scripts/customer_scenarios.py index eb806468e21..b81137042b9 100755 --- a/scripts/customer_scenarios.py +++ b/scripts/customer_scenarios.py @@ -19,8 +19,7 @@ def make_path_list(path_list): paths = path_list.split(',') paths = [path for path in paths if any(target in path for target in targets)] return set(paths) - else: - return targets + return targets def get_bz_data(paths): @@ -31,12 +30,10 @@ def get_bz_data(paths): for test in tests: test_dict = test.to_dict() test_data = {**test_dict['tokens'], **test_dict['invalid-tokens']} - if 'bz' in test_data.keys(): - if ( - 'customerscenario' not in test_data.keys() - or test_data['customerscenario'] == 'false' - ): - path_result.append([test.name, test_data['bz']]) + if 'bz' in test_data and ( + 'customerscenario' not in test_data or test_data['customerscenario'] == 'false' + ): + path_result.append([test.name, test_data['bz']]) if path_result: result[path] = path_result return result @@ -64,8 +61,7 @@ def get_response(bzs): ) assert response.status_code == 200, 'BZ query unsuccessful' assert response.json().get('error') is not True, response.json().get('message') - bugs = response.json().get('bugs') - return bugs + return response.json().get('bugs') def query_bz(data): @@ -75,7 +71,7 @@ def query_bz(data): for test in tests: bugs = get_response(test[1]) for bug in bugs: - if 'external_bugs' in bug.keys() and len(bug['external_bugs']) > 1: + if 'external_bugs' in bug and len(bug['external_bugs']) > 1: customer_cases = [ case for case in bug['external_bugs'] diff --git a/scripts/fixture_cli.py b/scripts/fixture_cli.py index 5963394e21f..c68559d3c1b 100644 --- a/scripts/fixture_cli.py +++ b/scripts/fixture_cli.py @@ -16,13 +16,13 @@ def fixture_to_test(fixture_name): """ if ":" not in fixture_name: return f"def test_runfake_{fixture_name}({fixture_name}):\n assert True" - else: - fixture_name, params = fixture_name.split(":") - params = params.split(",") - return ( - f"@pytest.mark.parametrize('{fixture_name}', {params}, indirect=True)\n" - f"def test_runfake_{fixture_name}({fixture_name}):\n assert True" - ) + + fixture_name, params = fixture_name.split(":") + params = params.split(",") + return ( + f"@pytest.mark.parametrize('{fixture_name}', {params}, indirect=True)\n" + f"def test_runfake_{fixture_name}({fixture_name}):\n assert True" + ) @click.command() diff --git a/scripts/graph_entities.py b/scripts/graph_entities.py index a404c616911..46038a3e3b0 100755 --- a/scripts/graph_entities.py +++ b/scripts/graph_entities.py @@ -24,9 +24,7 @@ def graph(): for entity_name, entity in entities_.items(): # Graph out which entities this entity depends on. for field_name, field in entity.get_fields().items(): - if isinstance(field, entity_mixins.OneToOneField) or isinstance( - field, entity_mixins.OneToManyField - ): + if isinstance(field, (entity_mixins.OneToOneField | entity_mixins.OneToManyField)): print( '{} -> {} [label="{}"{}]'.format( entity_name, diff --git a/testimony.yaml b/testimony.yaml index e6c210965a2..1088ac8f33a 100644 --- a/testimony.yaml +++ b/testimony.yaml @@ -1,6 +1,8 @@ Team: required: true BZ: {} +BlockedBy: {} +Verifies: {} CaseAutomation: casesensitive: true choices: @@ -15,7 +17,8 @@ CaseComponent: # No spaces allowed - ActivationKeys - AlternateContentSources - - Ansible + - Ansible-ConfigurationManagement + - Ansible-RemoteExecution - AnsibleCollection - API - AuditLog @@ -25,9 +28,8 @@ CaseComponent: - Branding - BVT - Candlepin - - Capsule + - ForemanProxy - Capsule-Content - - Certificates - ComputeResources - ComputeResources-Azure - ComputeResources-CNV @@ -47,13 +49,11 @@ CaseComponent: - DiscoveryImage - DiscoveryPlugin - Documentation - - Dynflow - - Email - Entitlements - ErrataManagement - Fact - ForemanDebug - - ForemanMaintain + - SatelliteMaintain - Hammer - Hammer-Content - HTTPProxy @@ -64,10 +64,9 @@ CaseComponent: - Hosts-Content - Infobloxintegration - Infrastructure - - Installer + - Installation - InterSatelliteSync - katello-tracer - - LDAP - Leappintegration - LifecycleEnvironments - LocalizationInternationalization @@ -76,7 +75,6 @@ CaseComponent: - Networking - Notifications - OrganizationsandLocations - - Packaging - Parameters - Provisioning - ProvisioningTemplates @@ -89,16 +87,12 @@ CaseComponent: - RemoteExecution - Reporting - Repositories - - RHCloud-CloudConnector - - RHCloud-Insights - - RHCloud-Inventory + - RHCloud - rubygem-foreman-redhat_access - - satellite-change-hostname - SatelliteClone - SCAPPlugin - Search - Security - - SELinux - Settings - SubscriptionManagement - Subscriptions-virt-who diff --git a/tests/foreman/api/test_activationkey.py b/tests/foreman/api/test_activationkey.py index 56513c03d85..ef5add381fc 100644 --- a/tests/foreman/api/test_activationkey.py +++ b/tests/foreman/api/test_activationkey.py @@ -171,7 +171,7 @@ def test_positive_update_limited_host(max_host, target_sat): for key, value in want.items(): setattr(act_key, key, value) act_key = act_key.update(want.keys()) - actual = {attr: getattr(act_key, attr) for attr in want.keys()} + actual = {attr: getattr(act_key, attr) for attr in want} assert want == actual @@ -219,7 +219,7 @@ def test_negative_update_limit(max_host, target_sat): with pytest.raises(HTTPError): act_key.update(want.keys()) act_key = act_key.read() - actual = {attr: getattr(act_key, attr) for attr in want.keys()} + actual = {attr: getattr(act_key, attr) for attr in want} assert want == actual @@ -293,7 +293,7 @@ def test_positive_get_releases_content(target_sat): """ act_key = target_sat.api.ActivationKey().create() response = client.get(act_key.path('releases'), auth=get_credentials(), verify=False).json() - assert 'results' in response.keys() + assert 'results' in response assert isinstance(response['results'], list) diff --git a/tests/foreman/api/test_ansible.py b/tests/foreman/api/test_ansible.py index cc491b29ea8..435501d0696 100644 --- a/tests/foreman/api/test_ansible.py +++ b/tests/foreman/api/test_ansible.py @@ -4,11 +4,11 @@ :CaseAutomation: Automated -:CaseComponent: Ansible +:CaseComponent: Ansible-ConfigurationManagement :Team: Rocket -:CaseImportance: High +:CaseImportance: Critical """ from fauxfactory import gen_string @@ -19,159 +19,345 @@ from robottelo.utils.issue_handlers import is_open -@pytest.mark.e2e -def test_fetch_and_sync_ansible_playbooks(target_sat): +@pytest.fixture +def filtered_user(target_sat, module_org, module_location): """ - Test Ansible Playbooks api for fetching and syncing playbooks - - :id: 17b4e767-1494-4960-bc60-f31a0495c09f - - :customerscenario: true - :steps: + 1. Create a role with a host view filtered + 2. Create a user with that role + 3. Setup a host + """ + role = target_sat.api.Role( + name=gen_string('alpha'), location=[module_location], organization=[module_org] + ).create() + # assign view_hosts (with a filter, to test BZ 1699188), + # view_hostgroups, view_facts permissions to the role + permission_hosts = target_sat.api.Permission().search(query={'search': 'name="view_hosts"'}) + permission_hostgroups = target_sat.api.Permission().search( + query={'search': 'name="view_hostgroups"'} + ) + permission_facts = target_sat.api.Permission().search(query={'search': 'name="view_facts"'}) + target_sat.api.Filter( + permission=permission_hosts, search='name != nonexistent', role=role + ).create() + target_sat.api.Filter(permission=permission_hostgroups, role=role).create() + target_sat.api.Filter(permission=permission_facts, role=role).create() - 1. Install ansible collection with playbooks. - 2. Try to fetch the playbooks via api. - 3. Sync the playbooks. - 4. Assert the count of playbooks fetched and synced are equal. + password = gen_string('alpha') + user = target_sat.api.User( + role=[role], password=password, location=[module_location], organization=[module_org] + ).create() - :expectedresults: - 1. Playbooks should be fetched and synced successfully. + return user, password - :BZ: 2115686 - :CaseAutomation: Automated - """ - target_sat.execute( - "ansible-galaxy collection install -p /usr/share/ansible/collections " - "xprazak2.forklift_collection" - ) - proxy_id = target_sat.nailgun_smart_proxy.id - playbook_fetch = target_sat.api.AnsiblePlaybooks().fetch(data={'proxy_id': proxy_id}) - playbooks_count = len(playbook_fetch['results']['playbooks_names']) - playbook_sync = target_sat.api.AnsiblePlaybooks().sync(data={'proxy_id': proxy_id}) - assert playbook_sync['action'] == "Sync playbooks" - - target_sat.wait_for_tasks( - search_query=(f'id = {playbook_sync["id"]}'), - poll_timeout=100, - ) - task_details = target_sat.api.ForemanTask().search( - query={'search': f'id = {playbook_sync["id"]}'} - ) - assert task_details[0].result == 'success' - assert len(task_details[0].output['result']['created']) == playbooks_count +@pytest.mark.upgrade +class TestAnsibleCfgMgmt: + """Test class for Configuration Management with Ansible + :CaseComponent: Ansible-ConfigurationManagement -@pytest.mark.e2e -@pytest.mark.no_containers -@pytest.mark.rhel_ver_match('[^6].*') -def test_positive_ansible_job_on_host( - target_sat, module_org, module_location, module_ak_with_synced_repo, rhel_contenthost -): """ - Test successful execution of Ansible Job on host. - :id: c8dcdc54-cb98-4b24-bff9-049a6cc36acb + @pytest.mark.e2e + def test_fetch_and_sync_ansible_playbooks(self, target_sat): + """ + Test Ansible Playbooks api for fetching and syncing playbooks - :steps: - 1. Register a content host with satellite - 2. Import a role into satellite - 3. Assign that role to a host - 4. Assert that the role was assigned to the host successfully - 5. Run the Ansible playbook associated with that role - 6. Check if the job is executed. + :id: 17b4e767-1494-4960-bc60-f31a0495c09f - :expectedresults: - 1. Host should be assigned the proper role. - 2. Job execution must be successful. + :steps: + 1. Install ansible collection with playbooks. + 2. Try to fetch the playbooks via api. + 3. Sync the playbooks. + 4. Assert the count of playbooks fetched and synced are equal. - :BZ: 2164400 + :expectedresults: + 1. Playbooks should be fetched and synced successfully. - :CaseAutomation: Automated + :BZ: 2115686 - :CaseImportance: Critical - """ - SELECTED_ROLE = 'RedHatInsights.insights-client' - if rhel_contenthost.os_version.major <= 7: - rhel_contenthost.create_custom_repos(rhel7=settings.repos.rhel7_os) - assert rhel_contenthost.execute('yum install -y insights-client').status == 0 - result = rhel_contenthost.register( - module_org, module_location, module_ak_with_synced_repo.name, target_sat - ) - assert result.status == 0, f'Failed to register host: {result.stderr}' - proxy_id = target_sat.nailgun_smart_proxy.id - target_host = rhel_contenthost.nailgun_host - target_sat.api.AnsibleRoles().sync(data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]}) - role_id = target_sat.api.AnsibleRoles().search(query={'search': f'name={SELECTED_ROLE}'})[0].id - target_sat.api.Host(id=target_host.id).add_ansible_role(data={'ansible_role_id': role_id}) - host_roles = target_host.list_ansible_roles() - assert host_roles[0]['name'] == SELECTED_ROLE - assert target_host.name == rhel_contenthost.hostname - - template_id = ( - target_sat.api.JobTemplate() - .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] - .id - ) - job = target_sat.api.JobInvocation().run( - synchronous=False, - data={ - 'job_template_id': template_id, - 'targeting_type': 'static_query', - 'search_query': f'name = {rhel_contenthost.hostname}', - }, - ) - target_sat.wait_for_tasks( - f'resource_type = JobInvocation and resource_id = {job["id"]}', poll_timeout=1000 - ) - result = target_sat.api.JobInvocation(id=job['id']).read() - assert result.succeeded == 1 - target_sat.api.Host(id=target_host.id).remove_ansible_role(data={'ansible_role_id': role_id}) - host_roles = target_host.list_ansible_roles() - assert len(host_roles) == 0 + :customerscenario: true + """ + target_sat.execute( + "ansible-galaxy collection install -p /usr/share/ansible/collections " + "xprazak2.forklift_collection" + ) + proxy_id = target_sat.nailgun_smart_proxy.id + playbook_fetch = target_sat.api.AnsiblePlaybooks().fetch(data={'proxy_id': proxy_id}) + playbooks_count = len(playbook_fetch['results']['playbooks_names']) + playbook_sync = target_sat.api.AnsiblePlaybooks().sync(data={'proxy_id': proxy_id}) + assert playbook_sync['action'] == "Sync playbooks" + + target_sat.wait_for_tasks( + search_query=(f'id = {playbook_sync["id"]}'), + poll_timeout=100, + ) + task_details = target_sat.api.ForemanTask().search( + query={'search': f'id = {playbook_sync["id"]}'} + ) + assert task_details[0].result == 'success' + assert len(task_details[0].output['result']['created']) == playbooks_count + + @pytest.mark.e2e + @pytest.mark.tier2 + def test_add_and_remove_ansible_role_hostgroup(self, target_sat): + """ + Test add and remove functionality for ansible roles in hostgroup via API + + :id: 7672cf86-fa31-11ed-855a-0fd307d2d66b + + :steps: + 1. Create a hostgroup and a nested hostgroup + 2. Sync a few ansible roles + 3. Assign a few ansible roles with the host group + 4. Add some ansible role with the host group + 5. Add some ansible roles to the nested hostgroup + 6. Remove the added ansible roles from the parent and nested hostgroup + + :expectedresults: + 1. Ansible role assign/add/remove functionality should work as expected in API + + :BZ: 2164400 + """ + ROLE_NAMES = [ + 'theforeman.foreman_scap_client', + 'redhat.satellite.hostgroups', + 'RedHatInsights.insights-client', + 'redhat.satellite.compute_resources', + ] + hg = target_sat.api.HostGroup(name=gen_string('alpha')).create() + hg_nested = target_sat.api.HostGroup(name=gen_string('alpha'), parent=hg).create() + proxy_id = target_sat.nailgun_smart_proxy.id + target_sat.api.AnsibleRoles().sync(data={'proxy_id': proxy_id, 'role_names': ROLE_NAMES}) + ROLES = [ + target_sat.api.AnsibleRoles().search(query={'search': f'name={role}'})[0].id + for role in ROLE_NAMES + ] + # Assign first 2 roles to HG and verify it + target_sat.api.HostGroup(id=hg.id).assign_ansible_roles( + data={'ansible_role_ids': ROLES[:2]} + ) + for r1, r2 in zip( + target_sat.api.HostGroup(id=hg.id).list_ansible_roles(), ROLE_NAMES[:2], strict=True + ): + assert r1['name'] == r2 + + # Add next role from list to HG and verify it + target_sat.api.HostGroup(id=hg.id).add_ansible_role(data={'ansible_role_id': ROLES[2]}) + for r1, r2 in zip( + target_sat.api.HostGroup(id=hg.id).list_ansible_roles(), ROLE_NAMES[:3], strict=True + ): + assert r1['name'] == r2 + + # Add next role to nested HG, and verify roles are also nested to HG along with assigned role + # Also, ensure the parent HG does not contain the roles assigned to nested HGs + target_sat.api.HostGroup(id=hg_nested.id).add_ansible_role( + data={'ansible_role_id': ROLES[3]} + ) + for r1, r2 in zip( + target_sat.api.HostGroup(id=hg_nested.id).list_ansible_roles(), + [ROLE_NAMES[-1]] + ROLE_NAMES[:-1], + strict=True, + ): + assert r1['name'] == r2 + + for r1, r2 in zip( + target_sat.api.HostGroup(id=hg.id).list_ansible_roles(), ROLE_NAMES[:3], strict=True + ): + assert r1['name'] == r2 + + # Remove roles assigned one by one from HG and nested HG + for role in ROLES[:3]: + target_sat.api.HostGroup(id=hg.id).remove_ansible_role(data={'ansible_role_id': role}) + hg_roles = target_sat.api.HostGroup(id=hg.id).list_ansible_roles() + assert len(hg_roles) == 0 + + for role in ROLES: + target_sat.api.HostGroup(id=hg_nested.id).remove_ansible_role( + data={'ansible_role_id': role} + ) + hg_nested_roles = target_sat.api.HostGroup(id=hg_nested.id).list_ansible_roles() + assert len(hg_nested_roles) == 0 + + @pytest.mark.e2e + @pytest.mark.tier2 + def test_positive_ansible_roles_inherited_from_hostgroup( + self, request, target_sat, module_org, module_location + ): + """Verify ansible roles inheritance functionality for host with parent/nested hostgroup via API + :id: 7672cf86-fa31-11ed-855a-0fd307d2d66g -@pytest.mark.no_containers -def test_positive_ansible_job_on_multiple_host( - target_sat, - module_org, - rhel9_contenthost, - rhel8_contenthost, - rhel7_contenthost, - module_location, - module_ak_with_synced_repo, -): - """Test execution of Ansible job on multiple hosts simultaneously. + :steps: + 1. Create a host, hostgroup and nested hostgroup + 2. Sync a few ansible roles + 3. Assign a few ansible roles to the host, hostgroup, nested hostgroup and verify it. + 4. Update host to be in parent/nested hostgroup and verify roles assigned - :id: 9369feef-466c-40d3-9d0d-65520d7f21ef + :expectedresults: + 1. Hosts in parent/nested hostgroups must have direct and indirect roles correctly assigned. - :customerscenario: true + :BZ: 2187967 - :steps: - 1. Register multiple content hosts with satellite - 2. Import a role into satellite - 3. Assign that role to all host - 4. Trigger ansible job keeping all host in a single query - 5. Check the passing and failing of individual hosts - 6. Check if one of the job on a host is failed resulting into whole job is marked as failed. + :customerscenario: true + """ + ROLE_NAMES = [ + 'theforeman.foreman_scap_client', + 'RedHatInsights.insights-client', + 'redhat.satellite.compute_resources', + ] + proxy_id = target_sat.nailgun_smart_proxy.id + host = target_sat.api.Host(organization=module_org, location=module_location).create() + hg = target_sat.api.HostGroup(name=gen_string('alpha'), organization=[module_org]).create() + hg_nested = target_sat.api.HostGroup( + name=gen_string('alpha'), parent=hg, organization=[module_org] + ).create() + + @request.addfinalizer + def _finalize(): + host.delete() + hg_nested.delete() + hg.delete() + + target_sat.api.AnsibleRoles().sync(data={'proxy_id': proxy_id, 'role_names': ROLE_NAMES}) + ROLES = [ + target_sat.api.AnsibleRoles().search(query={'search': f'name={role}'})[0].id + for role in ROLE_NAMES + ] + + # Assign roles to Host/Hostgroup/Nested Hostgroup and verify it + target_sat.api.Host(id=host.id).add_ansible_role(data={'ansible_role_id': ROLES[0]}) + assert ROLE_NAMES[0] == target_sat.api.Host(id=host.id).list_ansible_roles()[0]['name'] + + target_sat.api.HostGroup(id=hg.id).add_ansible_role(data={'ansible_role_id': ROLES[1]}) + assert ROLE_NAMES[1] == target_sat.api.HostGroup(id=hg.id).list_ansible_roles()[0]['name'] + + target_sat.api.HostGroup(id=hg_nested.id).add_ansible_role( + data={'ansible_role_id': ROLES[2]} + ) + listroles = target_sat.api.HostGroup(id=hg_nested.id).list_ansible_roles() + assert ROLE_NAMES[2] == listroles[0]['name'] + assert listroles[0]['directly_assigned'] + assert ROLE_NAMES[1] == listroles[1]['name'] + assert not listroles[1]['directly_assigned'] + + # Update host to be in nested hostgroup and verify roles assigned + host.hostgroup = hg_nested + host = host.update(['hostgroup']) + listroles_host = target_sat.api.Host(id=host.id).list_ansible_roles() + assert ROLE_NAMES[0] == listroles_host[0]['name'] + assert listroles_host[0]['directly_assigned'] + assert ROLE_NAMES[1] == listroles_host[1]['name'] + assert not listroles_host[1]['directly_assigned'] + assert ROLE_NAMES[2] == listroles_host[2]['name'] + assert not listroles_host[1]['directly_assigned'] + # Verify nested hostgroup doesn't contains the roles assigned to host + listroles_nested_hg = target_sat.api.HostGroup(id=hg_nested.id).list_ansible_roles() + assert ROLE_NAMES[0] not in [role['name'] for role in listroles_nested_hg] + assert ROLE_NAMES[2] == listroles_nested_hg[0]['name'] + assert ROLE_NAMES[1] == listroles_nested_hg[1]['name'] + + # Update host to be in parent hostgroup and verify roles assigned + host.hostgroup = hg + host = host.update(['hostgroup']) + listroles = target_sat.api.Host(id=host.id).list_ansible_roles() + assert ROLE_NAMES[0] == listroles[0]['name'] + assert listroles[0]['directly_assigned'] + assert ROLE_NAMES[1] == listroles[1]['name'] + assert not listroles[1]['directly_assigned'] + # Verify parent hostgroup doesn't contains the roles assigned to host + listroles_hg = target_sat.api.HostGroup(id=hg.id).list_ansible_roles() + assert ROLE_NAMES[0] not in [role['name'] for role in listroles_hg] + assert ROLE_NAMES[1] == listroles_hg[0]['name'] + + @pytest.mark.rhel_ver_match('[78]') + @pytest.mark.tier2 + def test_positive_read_facts_with_filter( + self, target_sat, rex_contenthost, filtered_user, module_org, module_location + ): + """Read host's Ansible facts as a user with a role that has host filter + + :id: 483d5faf-7a4c-4cb7-b14f-369768ad99b0 + + :steps: + 1. Run Ansible roles on a host + 2. Using API, read Ansible facts of that host + + :expectedresults: Ansible facts returned + + :BZ: 1699188 + + :customerscenario: true + """ + user, password = filtered_user + host = rex_contenthost.nailgun_host + host.organization = module_org + host.location = module_location + host.update(['organization', 'location']) + + # gather ansible facts by running ansible roles on the host + host.play_ansible_roles() + if is_open('BZ:2216471'): + wait_for( + lambda: len(rex_contenthost.nailgun_host.get_facts()) > 0, + timeout=30, + delay=2, + ) + user_cfg = user_nailgun_config(user.login, password) + # get facts through API + user_facts = ( + target_sat.api.Host(server_config=user_cfg) + .search(query={'search': f'name={rex_contenthost.hostname}'})[0] + .get_facts() + ) + assert 'subtotal' in user_facts + assert user_facts['subtotal'] == 1 + assert 'results' in user_facts + assert rex_contenthost.hostname in user_facts['results'] + assert len(user_facts['results'][rex_contenthost.hostname]) > 0 - :expectedresults: - 1. One of the jobs failing on a single host must impact the overall result as failed. - :BZ: 2167396, 2190464, 2184117 +class TestAnsibleREX: + """Test class for remote execution via Ansible - :CaseAutomation: Automated + :CaseComponent: Ansible-RemoteExecution """ - hosts = [rhel9_contenthost, rhel8_contenthost, rhel7_contenthost] - SELECTED_ROLE = 'RedHatInsights.insights-client' - for host in hosts: - result = host.register( + + @pytest.mark.e2e + @pytest.mark.no_containers + @pytest.mark.rhel_ver_match('[^6].*') + def test_positive_ansible_job_on_host( + self, target_sat, module_org, module_location, module_ak_with_synced_repo, rhel_contenthost + ): + """Test successful execution of Ansible Job on host. + + :id: c8dcdc54-cb98-4b24-bff9-049a6cc36acb + + :steps: + 1. Register a content host with satellite + 2. Import a role into satellite + 3. Assign that role to a host + 4. Assert that the role was assigned to the host successfully + 5. Run the Ansible playbook associated with that role + 6. Check if the job is executed. + + :expectedresults: + 1. Host should be assigned the proper role. + 2. Job execution must be successful. + + :BZ: 2164400 + """ + SELECTED_ROLE = 'RedHatInsights.insights-client' + if rhel_contenthost.os_version.major <= 7: + rhel_contenthost.create_custom_repos(rhel7=settings.repos.rhel7_os) + assert rhel_contenthost.execute('yum install -y insights-client').status == 0 + result = rhel_contenthost.register( module_org, module_location, module_ak_with_synced_repo.name, target_sat ) assert result.status == 0, f'Failed to register host: {result.stderr}' proxy_id = target_sat.nailgun_smart_proxy.id - target_host = host.nailgun_host + target_host = rhel_contenthost.nailgun_host target_sat.api.AnsibleRoles().sync( data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]} ) @@ -181,192 +367,105 @@ def test_positive_ansible_job_on_multiple_host( target_sat.api.Host(id=target_host.id).add_ansible_role(data={'ansible_role_id': role_id}) host_roles = target_host.list_ansible_roles() assert host_roles[0]['name'] == SELECTED_ROLE + assert target_host.name == rhel_contenthost.hostname - template_id = ( - target_sat.api.JobTemplate() - .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] - .id - ) - job = target_sat.api.JobInvocation().run( - synchronous=False, - data={ - 'job_template_id': template_id, - 'targeting_type': 'static_query', - 'search_query': f'name ^ ({hosts[0].hostname} && {hosts[1].hostname} ' - f'&& {hosts[2].hostname})', - }, - ) - target_sat.wait_for_tasks( - f'resource_type = JobInvocation and resource_id = {job["id"]}', - poll_timeout=1000, - must_succeed=False, - ) - result = target_sat.api.JobInvocation(id=job['id']).read() - assert result.succeeded == 2 # SELECTED_ROLE working on rhel8/rhel9 clients - assert result.failed == 1 # SELECTED_ROLE failing on rhel7 client - assert result.status_label == 'failed' - - -@pytest.mark.e2e -@pytest.mark.tier2 -@pytest.mark.upgrade -def test_add_and_remove_ansible_role_hostgroup(target_sat): - """ - Test add and remove functionality for ansible roles in hostgroup via API - - :id: 7672cf86-fa31-11ed-855a-0fd307d2d66b - - :steps: - 1. Create a hostgroup and a nested hostgroup - 2. Sync a few ansible roles - 3. Assign a few ansible roles with the host group - 4. Add some ansible role with the host group - 5. Add some ansible roles to the nested hostgroup - 6. Remove the added ansible roles from the parent and nested hostgroup - - :expectedresults: - 1. Ansible role assign/add/remove functionality should work as expected in API - - :BZ: 2164400 - """ - ROLE_NAMES = [ - 'theforeman.foreman_scap_client', - 'redhat.satellite.hostgroups', - 'RedHatInsights.insights-client', - 'redhat.satellite.compute_resources', - ] - hg = target_sat.api.HostGroup(name=gen_string('alpha')).create() - hg_nested = target_sat.api.HostGroup(name=gen_string('alpha'), parent=hg).create() - proxy_id = target_sat.nailgun_smart_proxy.id - target_sat.api.AnsibleRoles().sync(data={'proxy_id': proxy_id, 'role_names': ROLE_NAMES}) - ROLES = [ - target_sat.api.AnsibleRoles().search(query={'search': f'name={role}'})[0].id - for role in ROLE_NAMES - ] - # Assign first 2 roles to HG and verify it - target_sat.api.HostGroup(id=hg.id).assign_ansible_roles(data={'ansible_role_ids': ROLES[:2]}) - for r1, r2 in zip( - target_sat.api.HostGroup(id=hg.id).list_ansible_roles(), ROLE_NAMES[:2], strict=True - ): - assert r1['name'] == r2 - - # Add next role from list to HG and verify it - target_sat.api.HostGroup(id=hg.id).add_ansible_role(data={'ansible_role_id': ROLES[2]}) - for r1, r2 in zip( - target_sat.api.HostGroup(id=hg.id).list_ansible_roles(), ROLE_NAMES[:3], strict=True - ): - assert r1['name'] == r2 - - # Add next role to nested HG, and verify roles are also nested to HG along with assigned role - # Also, ensure the parent HG does not contain the roles assigned to nested HGs - target_sat.api.HostGroup(id=hg_nested.id).add_ansible_role(data={'ansible_role_id': ROLES[3]}) - for r1, r2 in zip( - target_sat.api.HostGroup(id=hg_nested.id).list_ansible_roles(), - [ROLE_NAMES[-1]] + ROLE_NAMES[:-1], - strict=True, - ): - assert r1['name'] == r2 - - for r1, r2 in zip( - target_sat.api.HostGroup(id=hg.id).list_ansible_roles(), ROLE_NAMES[:3], strict=True + template_id = ( + target_sat.api.JobTemplate() + .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] + .id + ) + job = target_sat.api.JobInvocation().run( + synchronous=False, + data={ + 'job_template_id': template_id, + 'targeting_type': 'static_query', + 'search_query': f'name = {rhel_contenthost.hostname}', + }, + ) + target_sat.wait_for_tasks( + f'resource_type = JobInvocation and resource_id = {job["id"]}', poll_timeout=1000 + ) + result = target_sat.api.JobInvocation(id=job['id']).read() + assert result.succeeded == 1 + target_sat.api.Host(id=target_host.id).remove_ansible_role( + data={'ansible_role_id': role_id} + ) + host_roles = target_host.list_ansible_roles() + assert len(host_roles) == 0 + + @pytest.mark.no_containers + def test_positive_ansible_job_on_multiple_host( + self, + target_sat, + module_org, + rhel9_contenthost, + rhel8_contenthost, + rhel7_contenthost, + module_location, + module_ak_with_synced_repo, ): - assert r1['name'] == r2 - - # Remove roles assigned one by one from HG and nested HG - for role in ROLES[:3]: - target_sat.api.HostGroup(id=hg.id).remove_ansible_role(data={'ansible_role_id': role}) - hg_roles = target_sat.api.HostGroup(id=hg.id).list_ansible_roles() - assert len(hg_roles) == 0 - - for role in ROLES: - target_sat.api.HostGroup(id=hg_nested.id).remove_ansible_role( - data={'ansible_role_id': role} + """Test execution of Ansible job on multiple hosts simultaneously. + + :id: 9369feef-466c-40d3-9d0d-65520d7f21ef + + :customerscenario: true + + :steps: + 1. Register multiple content hosts with satellite + 2. Import a role into satellite + 3. Assign that role to all host + 4. Trigger ansible job keeping all host in a single query + 5. Check the passing and failing of individual hosts + 6. Check if one of the job on a host is failed resulting into whole job is marked as failed. + + :expectedresults: + 1. One of the jobs failing on a single host must impact the overall result as failed. + + :BZ: 2167396, 2190464, 2184117 + """ + hosts = [rhel9_contenthost, rhel8_contenthost, rhel7_contenthost] + SELECTED_ROLE = 'RedHatInsights.insights-client' + for host in hosts: + result = host.register( + module_org, module_location, module_ak_with_synced_repo.name, target_sat + ) + assert result.status == 0, f'Failed to register host: {result.stderr}' + proxy_id = target_sat.nailgun_smart_proxy.id + target_host = host.nailgun_host + target_sat.api.AnsibleRoles().sync( + data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]} + ) + role_id = ( + target_sat.api.AnsibleRoles() + .search(query={'search': f'name={SELECTED_ROLE}'})[0] + .id + ) + target_sat.api.Host(id=target_host.id).add_ansible_role( + data={'ansible_role_id': role_id} + ) + host_roles = target_host.list_ansible_roles() + assert host_roles[0]['name'] == SELECTED_ROLE + + template_id = ( + target_sat.api.JobTemplate() + .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] + .id ) - hg_nested_roles = target_sat.api.HostGroup(id=hg_nested.id).list_ansible_roles() - assert len(hg_nested_roles) == 0 - - -@pytest.fixture -def filtered_user(target_sat, module_org, module_location): - """ - :steps: - 1. Create a role with a host view filtered - 2. Create a user with that role - 3. Setup a host - """ - api = target_sat.api - role = api.Role( - name=gen_string('alpha'), location=[module_location], organization=[module_org] - ).create() - # assign view_hosts (with a filter, to test BZ 1699188), - # view_hostgroups, view_facts permissions to the role - permission_hosts = api.Permission().search(query={'search': 'name="view_hosts"'}) - permission_hostgroups = api.Permission().search(query={'search': 'name="view_hostgroups"'}) - permission_facts = api.Permission().search(query={'search': 'name="view_facts"'}) - api.Filter(permission=permission_hosts, search='name != nonexistent', role=role).create() - api.Filter(permission=permission_hostgroups, role=role).create() - api.Filter(permission=permission_facts, role=role).create() - - password = gen_string('alpha') - user = api.User( - role=[role], password=password, location=[module_location], organization=[module_org] - ).create() - - return user, password - - -@pytest.fixture -def rex_host_in_org_and_loc(target_sat, module_org, module_location, rex_contenthost): - api = target_sat.api - host = api.Host().search(query={'search': f'name={rex_contenthost.hostname}'})[0] - host_id = host.id - api.Host(id=host_id, organization=[module_org.id]).update(['organization']) - api.Host(id=host_id, location=module_location.id).update(['location']) - return host - - -@pytest.mark.rhel_ver_match('[78]') -@pytest.mark.tier2 -def test_positive_read_facts_with_filter( - target_sat, rex_contenthost, filtered_user, rex_host_in_org_and_loc -): - """ - Read host's Ansible facts as a user with a role that has host filter - - :id: 483d5faf-7a4c-4cb7-b14f-369768ad99b0 - - 1. Run Ansible roles on a host - 2. Using API, read Ansible facts of that host - - :expectedresults: Ansible facts returned - - :BZ: 1699188 - - :customerscenario: true - """ - user, password = filtered_user - host = rex_host_in_org_and_loc - - # gather ansible facts by running ansible roles on the host - host.play_ansible_roles() - if is_open('BZ:2216471'): - host_wait = target_sat.api.Host().search( - query={'search': f'name={rex_contenthost.hostname}'} - )[0] - wait_for( - lambda: len(host_wait.get_facts()) > 0, - timeout=30, - delay=2, + job = target_sat.api.JobInvocation().run( + synchronous=False, + data={ + 'job_template_id': template_id, + 'targeting_type': 'static_query', + 'search_query': f'name ^ ({hosts[0].hostname} && {hosts[1].hostname} ' + f'&& {hosts[2].hostname})', + }, ) - - user_cfg = user_nailgun_config(user.login, password) - host = target_sat.api.Host(server_config=user_cfg).search( - query={'search': f'name={rex_contenthost.hostname}'} - )[0] - # get facts through API - facts = host.get_facts() - assert 'subtotal' in facts - assert facts['subtotal'] == 1 - assert 'results' in facts - assert rex_contenthost.hostname in facts['results'] - assert len(facts['results'][rex_contenthost.hostname]) > 0 + target_sat.wait_for_tasks( + f'resource_type = JobInvocation and resource_id = {job["id"]}', + poll_timeout=1000, + must_succeed=False, + ) + result = target_sat.api.JobInvocation(id=job['id']).read() + assert result.succeeded == 2 # SELECTED_ROLE working on rhel8/rhel9 clients + assert result.failed == 1 # SELECTED_ROLE failing on rhel7 client + assert result.status_label == 'failed' diff --git a/tests/foreman/api/test_bookmarks.py b/tests/foreman/api/test_bookmarks.py index b51ac194cb3..5d5833ef563 100644 --- a/tests/foreman/api/test_bookmarks.py +++ b/tests/foreman/api/test_bookmarks.py @@ -17,11 +17,11 @@ import pytest from requests.exceptions import HTTPError -from robottelo.constants import BOOKMARK_ENTITIES +from robottelo.constants import BOOKMARK_ENTITIES_SELECTION from robottelo.utils.datafactory import invalid_values_list, valid_data_list # List of unique bookmark controller values, preserving order -CONTROLLERS = list(dict.fromkeys(entity['controller'] for entity in BOOKMARK_ENTITIES)) +CONTROLLERS = list(dict.fromkeys(entity['controller'] for entity in BOOKMARK_ENTITIES_SELECTION)) @pytest.mark.tier1 diff --git a/tests/foreman/api/test_capsule.py b/tests/foreman/api/test_capsule.py index 3d499ccf16e..7cbb989f940 100644 --- a/tests/foreman/api/test_capsule.py +++ b/tests/foreman/api/test_capsule.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: Capsule +:CaseComponent: ForemanProxy :Team: Platform @@ -21,7 +21,7 @@ @pytest.mark.e2e @pytest.mark.upgrade @pytest.mark.tier1 -def test_positive_update_capsule(target_sat, module_capsule_configured): +def test_positive_update_capsule(request, pytestconfig, target_sat, module_capsule_configured): """Update various capsule properties :id: a3d3eaa9-ed8d-42e6-9c83-20251e5ca9af @@ -39,14 +39,15 @@ def test_positive_update_capsule(target_sat, module_capsule_configured): :customerscenario: true """ - new_name = f'{gen_string("alpha")}-{module_capsule_configured.name}' + new_name = f'{gen_string("alpha")}-{module_capsule_configured.hostname}' capsule = target_sat.api.SmartProxy().search( query={'search': f'name = {module_capsule_configured.hostname}'} )[0] # refresh features features = capsule.refresh() - module_capsule_configured.run_installer_arg('enable-foreman-proxy-plugin-openscap') + result = module_capsule_configured.install(cmd_args=['enable-foreman-proxy-plugin-openscap']) + assert result.status == 0, 'Installer failed when enabling OpenSCAP plugin.' features_new = capsule.refresh() assert len(features_new["features"]) == len(features["features"]) + 1 assert 'Openscap' in [feature["name"] for feature in features_new["features"]] @@ -68,6 +69,17 @@ def test_positive_update_capsule(target_sat, module_capsule_configured): capsule = capsule.update(['name']) assert capsule.name == new_name + @request.addfinalizer + def _finalize(): + # Updating the hostname back + if ( + cap := target_sat.api.SmartProxy().search(query={'search': f'name = {new_name}'}) + and pytestconfig.option.n_minus + ): + cap = cap[0] + cap.name = module_capsule_configured.hostname + cap.update(['name']) + # serching for non-default capsule BZ#2077824 capsules = target_sat.api.SmartProxy().search(query={'search': 'id != 1'}) assert len(capsules) > 0 diff --git a/tests/foreman/api/test_capsulecontent.py b/tests/foreman/api/test_capsulecontent.py index abbfd9f9f32..818a808a4c2 100644 --- a/tests/foreman/api/test_capsulecontent.py +++ b/tests/foreman/api/test_capsulecontent.py @@ -12,19 +12,35 @@ :CaseImportance: High """ -from datetime import datetime + +from datetime import datetime, timedelta import re from time import sleep from nailgun import client +from nailgun.config import ServerConfig from nailgun.entity_mixins import call_entity_method_with_timeout import pytest +from requests.exceptions import HTTPError -from robottelo import constants from robottelo.config import settings from robottelo.constants import ( + CONTAINER_CLIENTS, CONTAINER_REGISTRY_HUB, CONTAINER_UPSTREAM_NAME, + ENVIRONMENT, + FAKE_1_YUM_REPOS_COUNT, + FAKE_3_YUM_REPO_RPMS, + FAKE_3_YUM_REPOS_COUNT, + FAKE_FILE_LARGE_COUNT, + FAKE_FILE_LARGE_URL, + FAKE_FILE_NEW_NAME, + KICKSTART_CONTENT, + PRDS, + REPOS, + REPOSET, + RH_CONTAINER_REGISTRY_HUB, + RPM_TO_UPLOAD, DataFile, ) from robottelo.constants.repos import ANSIBLE_GALAXY, CUSTOM_FILE_REPO @@ -33,9 +49,25 @@ get_repomd, get_repomd_revision, ) +from robottelo.utils.datafactory import gen_string from robottelo.utils.issue_handlers import is_open +@pytest.fixture +def default_non_admin_user(target_sat, default_org, default_location): + """Non-admin user with no roles assigned in the Default org/loc.""" + password = gen_string('alphanumeric') + user = target_sat.api.User( + login=gen_string('alpha'), + password=password, + organization=[default_org], + location=[default_location], + ).create() + user.password = password + yield user + user.delete() + + @pytest.mark.run_in_one_thread class TestCapsuleContentManagement: """Content Management related tests, which exercise katello with pulp @@ -83,14 +115,14 @@ def test_positive_uploaded_content_library_sync( assert repo.read().content_counts['rpm'] == 1 + timestamp = datetime.utcnow().replace(microsecond=0) # Publish new version of the content view cv.publish() + # query sync status as publish invokes sync, task succeeds + module_capsule_configured.wait_for_sync(start_time=timestamp) cv = cv.read() - assert len(cv.version) == 1 - module_capsule_configured.wait_for_sync() - # Verify the RPM published on Capsule caps_repo_url = module_capsule_configured.get_published_repo_url( org=function_org.label, @@ -101,7 +133,7 @@ def test_positive_uploaded_content_library_sync( ) caps_files = get_repo_files_by_url(caps_repo_url) assert len(caps_files) == 1 - assert caps_files[0] == constants.RPM_TO_UPLOAD + assert caps_files[0] == RPM_TO_UPLOAD @pytest.mark.tier4 @pytest.mark.skip_if_not_set('capsule', 'clients', 'fake_manifest') @@ -149,13 +181,13 @@ def test_positive_checksum_sync( assert len(cv.version) == 1 cvv = cv.version[-1].read() + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': function_lce.id}) - cvv = cvv.read() + module_capsule_configured.wait_for_sync(start_time=timestamp) + cvv = cvv.read() assert len(cvv.environment) == 2 - module_capsule_configured.wait_for_sync() - # Verify repodata's checksum type is sha256, not sha1 on capsule repo_url = module_capsule_configured.get_published_repo_url( org=function_org.label, @@ -182,13 +214,13 @@ def test_positive_checksum_sync( cv.version.sort(key=lambda version: version.id) cvv = cv.version[-1].read() + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': function_lce.id}) - cvv = cvv.read() + module_capsule_configured.wait_for_sync(start_time=timestamp) + cvv = cvv.read() assert len(cvv.environment) == 2 - module_capsule_configured.wait_for_sync() - # Verify repodata's checksum type has updated to sha1 on capsule repomd = get_repomd(repo_url) checksum_types = re.findall(r'(?<=checksum type=").*?(?=")', repomd) @@ -257,12 +289,13 @@ def test_positive_sync_updated_repo( assert len(cv.version) == 1 cvv = cv.version[-1].read() + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': function_lce.id}) + + module_capsule_configured.wait_for_sync(start_time=timestamp) cvv = cvv.read() assert len(cvv.environment) == 2 - module_capsule_configured.wait_for_sync() - # Upload more content to the repository with open(DataFile.SRPM_TO_UPLOAD, 'rb') as handle: repo.upload_content(files={'content': handle}) @@ -276,12 +309,13 @@ def test_positive_sync_updated_repo( cv.version.sort(key=lambda version: version.id) cvv = cv.version[-1].read() + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': function_lce.id}) + + module_capsule_configured.wait_for_sync(start_time=timestamp) cvv = cvv.read() assert len(cvv.environment) == 2 - module_capsule_configured.wait_for_sync() - # Check the content is synced on the Capsule side properly sat_repo_url = target_sat.get_published_repo_url( org=function_org.label, @@ -357,10 +391,18 @@ def test_positive_capsule_sync( assert len(cv.version) == 1 cvv = cv.version[-1].read() - # Promote content view to lifecycle environment + # prior to trigger (promoting), assert no active sync tasks + active_tasks = module_capsule_configured.nailgun_capsule.content_get_sync( + synchronous=True, timeout=600 + )['active_sync_tasks'] + assert len(active_tasks) == 0 + # Promote content view to lifecycle environment, + # invoking capsule sync task(s) + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': function_lce.id}) - cvv = cvv.read() + module_capsule_configured.wait_for_sync(start_time=timestamp) + cvv = cvv.read() assert len(cvv.environment) == 2 # Content of the published content view in @@ -368,8 +410,6 @@ def test_positive_capsule_sync( # repository assert repo.content_counts['rpm'] == cvv.package_count - module_capsule_configured.wait_for_sync() - # Assert that the content published on the capsule is exactly the # same as in repository on satellite sat_repo_url = target_sat.get_published_repo_url( @@ -404,14 +444,14 @@ def test_positive_capsule_sync( cv = cv.read() cv.version.sort(key=lambda version: version.id) cvv = cv.version[-1].read() - # Promote new content view version to lifecycle environment + # Promote new content view version to lifecycle environment, + # capsule sync task(s) invoked and succeed + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': function_lce.id}) - cvv = cvv.read() + module_capsule_configured.wait_for_sync(start_time=timestamp) + cvv = cvv.read() assert len(cvv.environment) == 2 - - module_capsule_configured.wait_for_sync() - # Assert that the value of repomd revision of repository in # lifecycle environment on the capsule has not changed new_lce_revision_capsule = get_repomd_revision(caps_repo_url) @@ -427,21 +467,22 @@ def test_positive_capsule_sync( cv = cv.read() cv.version.sort(key=lambda version: version.id) cvv = cv.version[-1].read() + + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': function_lce.id}) - cvv = cvv.read() + module_capsule_configured.wait_for_sync(start_time=timestamp) + cvv = cvv.read() assert len(cvv.environment) == 2 # Assert that packages count in the repository is updated - assert repo.content_counts['rpm'] == (constants.FAKE_1_YUM_REPOS_COUNT + 1) + assert repo.content_counts['rpm'] == (FAKE_1_YUM_REPOS_COUNT + 1) # Assert that the content of the published content view in # lifecycle environment is exactly the same as content of the # repository assert repo.content_counts['rpm'] == cvv.package_count - module_capsule_configured.wait_for_sync() - # Assert that the content published on the capsule is exactly the # same as in the repository sat_files = get_repo_files_by_url(sat_repo_url) @@ -451,7 +492,7 @@ def test_positive_capsule_sync( @pytest.mark.tier4 @pytest.mark.skip_if_not_set('capsule', 'clients') def test_positive_iso_library_sync( - self, module_capsule_configured, module_entitlement_manifest_org, module_target_sat + self, module_capsule_configured, module_sca_manifest_org, module_target_sat ): """Ensure RH repo with ISOs after publishing to Library is synchronized to capsule automatically @@ -467,18 +508,18 @@ def test_positive_iso_library_sync( # Enable & sync RH repository with ISOs rh_repo_id = module_target_sat.api_factory.enable_rhrepo_and_fetchid( basearch='x86_64', - org_id=module_entitlement_manifest_org.id, - product=constants.PRDS['rhsc'], - repo=constants.REPOS['rhsc7_iso']['name'], - reposet=constants.REPOSET['rhsc7_iso'], + org_id=module_sca_manifest_org.id, + product=PRDS['rhsc'], + repo=REPOS['rhsc7_iso']['name'], + reposet=REPOSET['rhsc7_iso'], releasever=None, ) rh_repo = module_target_sat.api.Repository(id=rh_repo_id).read() call_entity_method_with_timeout(rh_repo.sync, timeout=2500) # Find "Library" lifecycle env for specific organization lce = module_target_sat.api.LifecycleEnvironment( - organization=module_entitlement_manifest_org - ).search(query={'search': f'name={constants.ENVIRONMENT}'})[0] + organization=module_sca_manifest_org + ).search(query={'search': f'name={ENVIRONMENT}'})[0] # Associate the lifecycle environment with the capsule module_capsule_configured.nailgun_capsule.content_add_lifecycle_environment( @@ -491,23 +532,23 @@ def test_positive_iso_library_sync( # Create a content view with the repository cv = module_target_sat.api.ContentView( - organization=module_entitlement_manifest_org, repository=[rh_repo] + organization=module_sca_manifest_org, repository=[rh_repo] ).create() # Publish new version of the content view + timestamp = datetime.utcnow() cv.publish() - cv = cv.read() + module_capsule_configured.wait_for_sync(start_time=timestamp) + cv = cv.read() assert len(cv.version) == 1 # Verify ISOs are present on satellite sat_isos = get_repo_files_by_url(rh_repo.full_path, extension='iso') assert len(sat_isos) == 4 - module_capsule_configured.wait_for_sync() - # Verify all the ISOs are present on capsule caps_path = ( - f'{module_capsule_configured.url}/pulp/content/{module_entitlement_manifest_org.label}' + f'{module_capsule_configured.url}/pulp/content/{module_sca_manifest_org.label}' f'/{lce.label}/{cv.label}/content/dist/rhel/server/7/7Server/x86_64/sat-capsule/6.4/' 'iso/' ) @@ -540,8 +581,8 @@ def test_positive_on_demand_sync( the original package from the upstream repo """ repo_url = settings.repos.yum_3.url - packages_count = constants.FAKE_3_YUM_REPOS_COUNT - package = constants.FAKE_3_YUM_REPO_RPMS[0] + packages_count = FAKE_3_YUM_REPOS_COUNT + package = FAKE_3_YUM_REPO_RPMS[0] repo = target_sat.api.Repository( download_policy='on_demand', mirroring_policy='mirror_complete', @@ -573,13 +614,13 @@ def test_positive_on_demand_sync( cvv = cv.version[-1].read() # Promote content view to lifecycle environment + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': function_lce.id}) - cvv = cvv.read() + module_capsule_configured.wait_for_sync(start_time=timestamp) + cvv = cvv.read() assert len(cvv.environment) == 2 - module_capsule_configured.wait_for_sync() - # Verify packages on Capsule match the source caps_repo_url = module_capsule_configured.get_published_repo_url( org=function_org.label, @@ -624,7 +665,7 @@ def test_positive_update_with_immediate_sync( filesystem contains valid links to packages """ repo_url = settings.repos.yum_1.url - packages_count = constants.FAKE_1_YUM_REPOS_COUNT + packages_count = FAKE_1_YUM_REPOS_COUNT repo = target_sat.api.Repository( download_policy='on_demand', mirroring_policy='mirror_complete', @@ -655,13 +696,13 @@ def test_positive_update_with_immediate_sync( cvv = cv.version[-1].read() # Promote content view to lifecycle environment + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': function_lce.id}) - cvv = cvv.read() + module_capsule_configured.wait_for_sync(start_time=timestamp) + cvv = cvv.read() assert len(cvv.environment) == 2 - module_capsule_configured.wait_for_sync() - # Update download policy to 'immediate' repo.download_policy = 'immediate' repo = repo.update(['download_policy']) @@ -683,13 +724,13 @@ def test_positive_update_with_immediate_sync( cv.version.sort(key=lambda version: version.id) cvv = cv.version[-1].read() # Promote content view to lifecycle environment + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': function_lce.id}) - cvv = cvv.read() + module_capsule_configured.wait_for_sync(start_time=timestamp) + cvv = cvv.read() assert len(cvv.environment) == 2 - module_capsule_configured.wait_for_sync() - # Verify the count of RPMs published on Capsule caps_repo_url = module_capsule_configured.get_published_repo_url( org=function_org.label, @@ -730,7 +771,7 @@ def test_positive_capsule_pub_url_accessible(self, module_capsule_configured): @pytest.mark.skip_if_not_set('capsule', 'clients') @pytest.mark.parametrize('distro', ['rhel7', 'rhel8_bos', 'rhel9_bos']) def test_positive_sync_kickstart_repo( - self, target_sat, module_capsule_configured, function_entitlement_manifest_org, distro + self, target_sat, module_capsule_configured, function_sca_manifest_org, distro ): """Sync kickstart repository to the capsule. @@ -751,16 +792,14 @@ def test_positive_sync_kickstart_repo( """ repo_id = target_sat.api_factory.enable_rhrepo_and_fetchid( basearch='x86_64', - org_id=function_entitlement_manifest_org.id, - product=constants.REPOS['kickstart'][distro]['product'], - reposet=constants.REPOS['kickstart'][distro]['reposet'], - repo=constants.REPOS['kickstart'][distro]['name'], - releasever=constants.REPOS['kickstart'][distro]['version'], + org_id=function_sca_manifest_org.id, + product=REPOS['kickstart'][distro]['product'], + reposet=REPOS['kickstart'][distro]['reposet'], + repo=REPOS['kickstart'][distro]['name'], + releasever=REPOS['kickstart'][distro]['version'], ) repo = target_sat.api.Repository(id=repo_id).read() - lce = target_sat.api.LifecycleEnvironment( - organization=function_entitlement_manifest_org - ).create() + lce = target_sat.api.LifecycleEnvironment(organization=function_sca_manifest_org).create() # Associate the lifecycle environment with the capsule module_capsule_configured.nailgun_capsule.content_add_lifecycle_environment( data={'environment_id': lce.id} @@ -775,7 +814,7 @@ def test_positive_sync_kickstart_repo( # Create a content view with the repository cv = target_sat.api.ContentView( - organization=function_entitlement_manifest_org, repository=[repo] + organization=function_sca_manifest_org, repository=[repo] ).create() # Sync repository repo.sync(timeout='10m') @@ -788,26 +827,26 @@ def test_positive_sync_kickstart_repo( cvv = cv.version[-1].read() # Promote content view to lifecycle environment + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': lce.id}) - cvv = cvv.read() + module_capsule_configured.wait_for_sync(start_time=timestamp) + cvv = cvv.read() assert len(cvv.environment) == 2 - module_capsule_configured.wait_for_sync() - # Check for kickstart content on SAT and CAPS tail = ( - f'rhel/server/7/{constants.REPOS["kickstart"][distro]["version"]}/x86_64/kickstart' + f'rhel/server/7/{REPOS["kickstart"][distro]["version"]}/x86_64/kickstart' if distro == 'rhel7' - else f'{distro.split("_")[0]}/{constants.REPOS["kickstart"][distro]["version"]}/x86_64/baseos/kickstart' # noqa:E501 + else f'{distro.split("_")[0]}/{REPOS["kickstart"][distro]["version"]}/x86_64/baseos/kickstart' # noqa:E501 ) url_base = ( - f'pulp/content/{function_entitlement_manifest_org.label}/{lce.label}/{cv.label}/' + f'pulp/content/{function_sca_manifest_org.label}/{lce.label}/{cv.label}/' f'content/dist/{tail}' ) # Check kickstart specific files - for file in constants.KICKSTART_CONTENT: + for file in KICKSTART_CONTENT: sat_file = target_sat.md5_by_url(f'{target_sat.url}/{url_base}/{file}') caps_file = target_sat.md5_by_url(f'{module_capsule_configured.url}/{url_base}/{file}') assert sat_file == caps_file @@ -887,12 +926,13 @@ def test_positive_sync_container_repo_end_to_end( # Promote the latest CV version into capsule's LCE cvv = cv.version[-1].read() + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': function_lce.id}) + + module_capsule_configured.wait_for_sync(start_time=timestamp) cvv = cvv.read() assert len(cvv.environment) == 2 - module_capsule_configured.wait_for_sync() - # Pull the images from capsule to the content host repo_paths = [ ( @@ -902,7 +942,7 @@ def test_positive_sync_container_repo_end_to_end( for repo in repos ] - for con_client in constants.CONTAINER_CLIENTS: + for con_client in CONTAINER_CLIENTS: result = container_contenthost.execute( f'{con_client} login -u {settings.server.admin_username}' f' -p {settings.server.admin_password} {module_capsule_configured.hostname}' @@ -1005,10 +1045,12 @@ def test_positive_sync_collection_repo( assert function_lce_library.id in [capsule_lce['id'] for capsule_lce in result['results']] # Sync the repo + timestamp = datetime.utcnow() repo.sync(timeout=600) repo = repo.read() assert repo.content_counts['ansible_collection'] == 2 - module_capsule_configured.wait_for_sync() + + module_capsule_configured.wait_for_sync(start_time=timestamp) repo_path = repo.full_path.replace(target_sat.hostname, module_capsule_configured.hostname) coll_path = './collections' @@ -1063,7 +1105,7 @@ def test_positive_sync_file_repo( repo = target_sat.api.Repository( content_type='file', product=function_product, - url=constants.FAKE_FILE_LARGE_URL, + url=FAKE_FILE_LARGE_URL, ).create() repo.sync() @@ -1087,12 +1129,13 @@ def test_positive_sync_file_repo( # Promote the latest CV version into capsule's LCE cvv = cv.version[-1].read() + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': function_lce.id}) + + module_capsule_configured.wait_for_sync(start_time=timestamp) cvv = cvv.read() assert len(cvv.environment) == 2 - module_capsule_configured.wait_for_sync() - # Run one more sync, check for status (BZ#1985122) sync_status = module_capsule_configured.nailgun_capsule.content_sync() assert sync_status['result'] == 'success' @@ -1114,8 +1157,8 @@ def test_positive_sync_file_repo( ) sat_files = get_repo_files_by_url(sat_repo_url, extension='iso') caps_files = get_repo_files_by_url(caps_repo_url, extension='iso') - assert len(sat_files) == len(caps_files) == constants.FAKE_FILE_LARGE_COUNT + 1 - assert constants.FAKE_FILE_NEW_NAME in caps_files + assert len(sat_files) == len(caps_files) == FAKE_FILE_LARGE_COUNT + 1 + assert FAKE_FILE_NEW_NAME in caps_files assert sat_files == caps_files for file in sat_files: @@ -1126,7 +1169,7 @@ def test_positive_sync_file_repo( @pytest.mark.tier4 @pytest.mark.skip_if_not_set('capsule') def test_positive_sync_CV_to_multiple_LCEs( - self, target_sat, module_capsule_configured, module_manifest_org + self, target_sat, module_capsule_configured, module_sca_manifest_org ): """Synchronize a CV to multiple LCEs at the same time. All sync tasks should succeed. @@ -1151,19 +1194,19 @@ def test_positive_sync_CV_to_multiple_LCEs( # Sync a repository to the Satellite. repo_id = target_sat.api_factory.enable_rhrepo_and_fetchid( basearch='x86_64', - org_id=module_manifest_org.id, - product=constants.PRDS['rhel'], - repo=constants.REPOS['rhel7_extra']['name'], - reposet=constants.REPOSET['rhel7_extra'], + org_id=module_sca_manifest_org.id, + product=PRDS['rhel'], + repo=REPOS['rhel7_extra']['name'], + reposet=REPOSET['rhel7_extra'], releasever=None, ) repo = target_sat.api.Repository(id=repo_id).read() repo.sync() # Create two LCEs, assign them to the Capsule. - lce1 = target_sat.api.LifecycleEnvironment(organization=module_manifest_org).create() + lce1 = target_sat.api.LifecycleEnvironment(organization=module_sca_manifest_org).create() lce2 = target_sat.api.LifecycleEnvironment( - organization=module_manifest_org, prior=lce1 + organization=module_sca_manifest_org, prior=lce1 ).create() module_capsule_configured.nailgun_capsule.content_add_lifecycle_environment( data={'environment_id': [lce1.id, lce2.id]} @@ -1175,7 +1218,7 @@ def test_positive_sync_CV_to_multiple_LCEs( # Create a Content View, add the repository and publish it. cv = target_sat.api.ContentView( - organization=module_manifest_org, repository=[repo] + organization=module_sca_manifest_org, repository=[repo] ).create() cv.publish() cv = cv.read() @@ -1183,16 +1226,20 @@ def test_positive_sync_CV_to_multiple_LCEs( # Promote the CV to both Capsule's LCEs without waiting for Capsule sync task completion. cvv = cv.version[-1].read() + assert len(cvv.environment) == 1 + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': lce1.id}) + + module_capsule_configured.wait_for_sync(start_time=timestamp) cvv = cvv.read() assert len(cvv.environment) == 2 + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': lce2.id}) + + module_capsule_configured.wait_for_sync(start_time=timestamp) cvv = cvv.read() assert len(cvv.environment) == 3 - # Check all sync tasks finished without errors. - module_capsule_configured.wait_for_sync() - @pytest.mark.tier4 @pytest.mark.skip_if_not_set('capsule') def test_positive_capsule_sync_status_persists( @@ -1235,7 +1282,8 @@ def test_positive_capsule_sync_status_persists( cvv = cv.version[-1].read() timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': function_lce.id}) - module_capsule_configured.wait_for_sync() + + module_capsule_configured.wait_for_sync(start_time=timestamp) # Delete all capsule sync tasks so that we fall back for audits. task_result = target_sat.execute( @@ -1263,8 +1311,9 @@ def test_positive_capsule_sync_status_persists( def test_positive_remove_capsule_orphans( self, target_sat, + pytestconfig, capsule_configured, - function_entitlement_manifest_org, + function_sca_manifest_org, function_lce_library, ): """Synchronize RPM content to the capsule, disassociate the capsule form the content @@ -1292,13 +1341,15 @@ def test_positive_remove_capsule_orphans( :BZ: 22043089, 2211962 """ + if pytestconfig.option.n_minus: + pytest.skip('Test cannot be run on n-minus setups session-scoped capsule') # Enable RHST repo and sync it to the Library LCE. repo_id = target_sat.api_factory.enable_rhrepo_and_fetchid( basearch='x86_64', - org_id=function_entitlement_manifest_org.id, - product=constants.REPOS['rhst8']['product'], - repo=constants.REPOS['rhst8']['name'], - reposet=constants.REPOSET['rhst8'], + org_id=function_sca_manifest_org.id, + product=REPOS['rhst8']['product'], + repo=REPOS['rhst8']['name'], + reposet=REPOSET['rhst8'], ) repo = target_sat.api.Repository(id=repo_id).read() repo.sync() @@ -1331,13 +1382,20 @@ def test_positive_remove_capsule_orphans( sync_status = capsule_configured.nailgun_capsule.content_sync() assert sync_status['result'] == 'success', 'Capsule sync task failed.' + # datetime string (local time) to search for proper task. + timestamp = (datetime.now().replace(microsecond=0) - timedelta(seconds=1)).strftime( + '%B %d, %Y at %I:%M:%S %p' + ) # Run orphan cleanup for the capsule. target_sat.execute( 'foreman-rake katello:delete_orphaned_content RAILS_ENV=production ' f'SMART_PROXY_ID={capsule_configured.nailgun_capsule.id}' ) target_sat.wait_for_tasks( - search_query=('label = Actions::Katello::OrphanCleanup::RemoveOrphans'), + search_query=( + 'label = Actions::Katello::OrphanCleanup::RemoveOrphans' + f' and started_at >= "{timestamp}"' + ), search_rate=5, max_tries=10, ) @@ -1388,7 +1446,7 @@ def test_positive_capsule_sync_openstack_container_repos( content_type='docker', docker_upstream_name=ups_name, product=function_product, - url=constants.RH_CONTAINER_REGISTRY_HUB, + url=RH_CONTAINER_REGISTRY_HUB, upstream_username=settings.subscription.rhn_username, upstream_password=settings.subscription.rhn_password, ).create() @@ -1411,12 +1469,13 @@ def test_positive_capsule_sync_openstack_container_repos( # Promote the latest CV version into capsule's LCE cvv = cv.version[-1].read() + timestamp = datetime.utcnow() cvv.promote(data={'environment_ids': function_lce.id}) + + module_capsule_configured.wait_for_sync(start_time=timestamp) cvv = cvv.read() assert len(cvv.environment) == 2 - module_capsule_configured.wait_for_sync() - @pytest.mark.parametrize( 'repos_collection', [ @@ -1513,7 +1572,7 @@ def test_positive_content_counts_for_mixed_cv( # Check the counts for CVV are not present at the Capsule side before sync. caps_counts = module_capsule_configured.nailgun_capsule.content_counts() - assert caps_counts is None or cvv.id not in caps_counts['content_view_versions'].keys() + assert caps_counts is None or cvv.id not in caps_counts['content_view_versions'] # Sync, wait for counts to be updated and get them from the Capsule. sync_status = module_capsule_configured.nailgun_capsule.content_sync() @@ -1528,7 +1587,7 @@ def test_positive_content_counts_for_mixed_cv( caps_counts = module_capsule_configured.nailgun_capsule.content_counts()[ 'content_view_versions' ] - assert str(cvv.id) in caps_counts.keys(), 'CVV is missing in content counts.' + assert str(cvv.id) in caps_counts, 'CVV is missing in content counts.' caps_counts = caps_counts[str(cvv.id)] # Every "environment repo" (the one promoted to an LCE and synced to the Capsule) @@ -1592,3 +1651,119 @@ def test_positive_content_counts_blank_update( assert ( counts is None or len(counts['content_view_versions']) == 0 ), f"No content counts expected, but got:\n{counts['content_view_versions']}." + + def test_positive_read_with_non_admin_user( + self, + target_sat, + module_capsule_configured, + default_org, + default_non_admin_user, + ): + """Try to list and read Capsules with a non-admin user with and without permissions. + + :id: f3ee19fa-9b91-4b49-b00a-8debee903ce6 + + :setup: + 1. Satellite with registered external Capsule. + 2. Non-admin user without any roles/permissions. + + :steps: + 1. Using the non-admin user try to list all or particular Capsule. + 2. Add Viewer role to the user and try again. + + :expectedresults: + 1. Read should fail without Viewer role. + 2. Read should succeed when Viewer role added. + + :BZ: 2096930 + + :customerscenario: true + """ + # Using the non-admin user try to list all or particular Capsule + user = default_non_admin_user + sc = ServerConfig( + auth=(user.login, user.password), + url=target_sat.url, + verify=settings.server.verify_ca, + ) + + with pytest.raises(HTTPError) as error: + target_sat.api.Capsule(server_config=sc).search() + assert error.value.response.status_code == 403 + assert 'Access denied' in error.value.response.text + + with pytest.raises(HTTPError) as error: + target_sat.api.Capsule( + server_config=sc, id=module_capsule_configured.nailgun_capsule.id + ).read() + assert error.value.response.status_code == 403 + assert 'Access denied' in error.value.response.text + + # Add Viewer role to the user and try again. + v_role = target_sat.api.Role().search(query={'search': 'name="Viewer"'}) + assert len(v_role) == 1, 'Expected just one Viewer to be found.' + user.role = [v_role[0]] + user.update(['role']) + + res = target_sat.api.Capsule(server_config=sc).search() + assert len(res) >= 2, 'Expected at least one internal and one or more external Capsule(s).' + assert {target_sat.hostname, module_capsule_configured.hostname}.issubset( + [caps.name for caps in res] + ), 'Internal and/or external Capsule was not listed.' + + res = target_sat.api.Capsule( + server_config=sc, id=module_capsule_configured.nailgun_capsule.id + ).read() + assert res.name == module_capsule_configured.hostname, 'External Capsule not found.' + + def test_positive_reclaim_space( + self, + target_sat, + module_capsule_configured, + ): + """Verify the reclaim_space endpoint spawns the Reclaim space task + and apidoc references the endpoint correctly. + + :id: eb16ed53-0489-4bb9-a0da-8d857a1c7d06 + + :setup: + 1. A registered external Capsule. + + :steps: + 1. Trigger the reclaim space task via API, check it succeeds. + 2. Check the apidoc references the correct endpoint. + + :expectedresults: + 1. Reclaim_space endpoint spawns the Reclaim space task and it succeeds. + 2. Apidoc references the correct endpoint. + + :CaseImportance: Medium + + :BZ: 2218179 + + :customerscenario: true + """ + # Trigger the reclaim space task via API, check it succeeds + task = module_capsule_configured.nailgun_capsule.content_reclaim_space() + assert task, 'No task was created for reclaim space.' + assert ( + 'Actions::Pulp3::CapsuleContent::ReclaimSpace' in task['label'] + ), 'Unexpected task triggered' + assert 'success' in task['result'], 'Reclaim task did not succeed' + + # Check the apidoc references the correct endpoint + try: + reclaim_doc = next( + method + for method in target_sat.apidoc['docs']['resources']['capsule_content']['methods'] + if '/apidoc/v2/capsule_content/reclaim_space' in method['doc_url'] + ) + except StopIteration: + raise AssertionError( + 'Could not find the reclaim_space apidoc at the expected path.' + ) from None + assert len(reclaim_doc['apis']) == 1 + assert reclaim_doc['apis'][0]['http_method'] == 'POST', 'POST method was expected.' + assert ( + reclaim_doc['apis'][0]['api_url'] == '/katello/api/capsules/:id/content/reclaim_space' + ), 'Documented path did not meet the expectation.' diff --git a/tests/foreman/api/test_computeresource_azurerm.py b/tests/foreman/api/test_computeresource_azurerm.py index 66f25f74b2b..47bf8313066 100644 --- a/tests/foreman/api/test_computeresource_azurerm.py +++ b/tests/foreman/api/test_computeresource_azurerm.py @@ -580,4 +580,4 @@ def test_positive_azurerm_custom_image_host_provisioned( # Azure cloud assert self.hostname.lower() == azureclient_host.name - assert AZURERM_VM_SIZE_DEFAULT == azureclient_host.type + assert azureclient_host.type == AZURERM_VM_SIZE_DEFAULT diff --git a/tests/foreman/api/test_computeresource_libvirt.py b/tests/foreman/api/test_computeresource_libvirt.py index 5189af9d445..ad95fb9f202 100644 --- a/tests/foreman/api/test_computeresource_libvirt.py +++ b/tests/foreman/api/test_computeresource_libvirt.py @@ -113,9 +113,9 @@ def test_positive_create_with_name_description( location=[module_location], url=LIBVIRT_URL, ).create() + request.addfinalizer(compresource.delete) assert compresource.name == name assert compresource.description == name - request.addfinalizer(compresource.delete) @pytest.mark.tier2 @@ -134,9 +134,9 @@ def test_positive_create_with_orgs_and_locs(request, module_target_sat): compresource = module_target_sat.api.LibvirtComputeResource( location=locs, organization=orgs, url=LIBVIRT_URL ).create() + request.addfinalizer(compresource.delete) assert {org.name for org in orgs} == {org.read().name for org in compresource.organization} assert {loc.name for loc in locs} == {loc.read().name for loc in compresource.location} - request.addfinalizer(compresource.delete) @pytest.mark.tier2 @@ -175,8 +175,8 @@ def test_negative_create_with_same_name(request, module_target_sat, module_org, cr = module_target_sat.api.LibvirtComputeResource( location=[module_location], name=name, organization=[module_org], url=LIBVIRT_URL ).create() - assert cr.name == name request.addfinalizer(cr.delete) + assert cr.name == name with pytest.raises(HTTPError): module_target_sat.api.LibvirtComputeResource( name=name, @@ -245,19 +245,16 @@ def test_negative_update_same_name(request, module_target_sat, module_org, modul compresource = module_target_sat.api.LibvirtComputeResource( location=[module_location], name=name, organization=[module_org], url=LIBVIRT_URL ).create() + request.addfinalizer(compresource.delete) new_compresource = module_target_sat.api.LibvirtComputeResource( location=[module_location], organization=[module_org], url=LIBVIRT_URL ).create() + request.addfinalizer(new_compresource.delete) new_compresource.name = name with pytest.raises(HTTPError): new_compresource.update(['name']) assert new_compresource.read().name != name - @request.addfinalizer - def _finalize(): - compresource.delete() - new_compresource.delete() - @pytest.mark.tier2 @pytest.mark.parametrize('url', **parametrized({'random': gen_string('alpha'), 'empty': ''})) diff --git a/tests/foreman/api/test_contentview.py b/tests/foreman/api/test_contentview.py index 73575dd4b1a..2cbeaf50860 100644 --- a/tests/foreman/api/test_contentview.py +++ b/tests/foreman/api/test_contentview.py @@ -11,6 +11,7 @@ :CaseImportance: High """ +from datetime import datetime, timedelta import random from fauxfactory import gen_integer, gen_string, gen_utf8 @@ -96,8 +97,7 @@ def apply_package_filter(content_view, repo, package, target_sat, inclusion=True assert cv_filter.id == cv_filter_rule.content_view_filter.id content_view.publish() content_view = content_view.read() - content_view_version_info = content_view.version[0].read() - return content_view_version_info + return content_view.version[0].read() class TestContentView: @@ -533,6 +533,54 @@ def test_positive_add_to_composite(self, content_view, module_org, module_target # composite CV → CV version → CV == CV assert composite_cv.component[0].read().content_view.id == content_view.id + @pytest.mark.tier2 + def test_negative_publish_during_repo_sync(self, content_view, module_target_sat): + """Attempt to publish a new version of the content-view, + while an associated repository is being synced. + + :id: c272fff7-a679-4844-a261-80830cdd5694 + + :BZ: 1957144 + + :steps: + 1. Add repository to content-view + 2. Perform asynchronous repository sync + 3. Attempt to publish a version of the content-view, while repo sync ongoing. + + :expectedresults: + 1. User cannot publish during repository sync. + 2. HTTP exception raised, assert publish task failed for expected reason, + repo sync task_id found in humanized error, content-view versions unchanged. + """ + # add repository to content-view + content_view.repository = [self.yum_repo] + content_view.update(['repository']) + content_view = content_view.read() + existing_versions = content_view.version + timestamp = (datetime.utcnow() - timedelta(seconds=1)).strftime('%Y-%m-%d %H:%M') + + # perform async repository sync, while still in progress- + # attempt to publish a new version of the content view. + repo_task_id = self.yum_repo.sync(synchronous=False)['id'] + with pytest.raises(HTTPError) as InternalServerError: + content_view.publish() + assert str(content_view.id) in str(InternalServerError) + + # search for failed publish task + task_action = 'Actions::Katello::ContentView::Publish' + task_search = module_target_sat.api.ForemanTask().search( + query={'search': f'{task_action} and started_at >= "{timestamp}"'} + ) + assert len(task_search) == 1 + task_id = task_search[0].id + # task failed for expected reason + task = module_target_sat.api.ForemanTask(id=task_id).poll(must_succeed=False) + assert task['result'] == 'error' + assert len(task['humanized']['errors']) == 1 + assert repo_task_id in task['humanized']['errors'][0] + # no new versions of content view, any existing remained the same + assert content_view.read().version == existing_versions + @pytest.mark.tier2 def test_negative_add_components_to_composite( self, content_view, module_org, module_target_sat diff --git a/tests/foreman/api/test_convert2rhel.py b/tests/foreman/api/test_convert2rhel.py index d7033ab427c..c78d06f0acc 100644 --- a/tests/foreman/api/test_convert2rhel.py +++ b/tests/foreman/api/test_convert2rhel.py @@ -291,8 +291,6 @@ def test_convert2rhel_oracle(module_target_sat, oracle, activation_key_rhel, ver or host_content['operatingsystem_name'].startswith(f'RedHat {version}') or host_content['operatingsystem_name'].startswith(f'RHEL {version}') ) - assert host_content['subscription_status_label'] == 'Simple Content Access' - assert host_content['subscription_status'] == 5 @pytest.mark.e2e @@ -349,5 +347,3 @@ def test_convert2rhel_centos(module_target_sat, centos, activation_key_rhel, ver or host_content['operatingsystem_name'].startswith(f'RedHat {version}') or host_content['operatingsystem_name'].startswith(f'RHEL {version}') ) - assert host_content['subscription_status_label'] == 'Simple Content Access' - assert host_content['subscription_status'] == 5 diff --git a/tests/foreman/api/test_discoveryrule.py b/tests/foreman/api/test_discoveryrule.py index f5dffd5be14..3d1c1f4bf48 100644 --- a/tests/foreman/api/test_discoveryrule.py +++ b/tests/foreman/api/test_discoveryrule.py @@ -171,8 +171,11 @@ def test_positive_multi_provision_with_rule_limit( :CaseImportance: High """ + discovered_host1 = module_target_sat.api_factory.create_discovered_host() + request.addfinalizer(module_target_sat.api.Host(id=discovered_host1['id']).delete) discovered_host2 = module_target_sat.api_factory.create_discovered_host() + request.addfinalizer(module_target_sat.api.DiscoveredHost(id=discovered_host2['id']).delete) rule = module_target_sat.api.DiscoveryRule( max_count=1, hostgroup=module_discovery_hostgroup, @@ -181,14 +184,6 @@ def test_positive_multi_provision_with_rule_limit( organization=[discovery_org], priority=1000, ).create() + request.addfinalizer(rule.delete) result = module_target_sat.api.DiscoveredHost().auto_provision_all() assert '1 discovered hosts were provisioned' in result['message'] - - # Delete discovery rule - @request.addfinalizer - def _finalize(): - rule.delete() - module_target_sat.api.Host(id=discovered_host1['id']).delete() - module_target_sat.api.DiscoveredHost(id=discovered_host2['id']).delete() - with pytest.raises(HTTPError): - rule.read() diff --git a/tests/foreman/api/test_errata.py b/tests/foreman/api/test_errata.py index 5bafb65d9b2..81974401a9f 100644 --- a/tests/foreman/api/test_errata.py +++ b/tests/foreman/api/test_errata.py @@ -76,12 +76,11 @@ def activation_key(module_sca_manifest_org, module_cv, module_lce, module_target module_cv, module_lce, )['content-view'] - activation_key = module_target_sat.api.ActivationKey( + return module_target_sat.api.ActivationKey( organization=module_sca_manifest_org, environment=module_lce, content_view=_cv, ).create() - return activation_key @pytest.fixture(scope='module') @@ -144,7 +143,7 @@ def _fetch_available_errata(host, expected_amount=None, timeout=120): for _ in range(timeout // 5): if expected_amount is None: return errata['results'] - elif len(errata['results']) == expected_amount: + if len(errata['results']) == expected_amount: return errata['results'] sleep(5) errata = host.errata() @@ -407,7 +406,7 @@ def package_applicability_changed_as_expected( prior_package = None # package must not have been present before this modification else: prior_package = package_filename - _applicables = { + return { 'result': True, 'errata_count': host.applicable_errata_count, 'package_count': host.applicable_package_count, @@ -416,7 +415,6 @@ def package_applicability_changed_as_expected( 'change_in_errata': change_in_errata, 'changed_errata': list(app_errata_diff_ids), } - return _applicables return True @@ -1347,6 +1345,7 @@ def _run_remote_command_on_content_host(command, vm, return_result=False): assert result.status == 0 if return_result: return result.stdout + return None def _set_prerequisites_for_swid_repos(vm): @@ -1354,7 +1353,6 @@ def _set_prerequisites_for_swid_repos(vm): f'curl --insecure --remote-name {settings.repos.swid_tools_repo}', vm ) _run_remote_command_on_content_host('mv *swid*.repo /etc/yum.repos.d', vm) - _run_remote_command_on_content_host(r'subscription-manager repos --enable \*', vm) _run_remote_command_on_content_host('yum install -y swid-tools', vm) _run_remote_command_on_content_host('yum install -y dnf-plugin-swidtags', vm) @@ -1366,37 +1364,47 @@ def _validate_swid_tags_installed(vm, module_name): assert module_name in result +@pytest.fixture +def errata_host_lce(module_sca_manifest_org, target_sat): + """Create and return a new lce in module SCA org.""" + return target_sat.api.LifecycleEnvironment(organization=module_sca_manifest_org).create() + + @pytest.mark.tier3 @pytest.mark.upgrade @pytest.mark.pit_client -@pytest.mark.parametrize( - 'function_repos_collection_with_manifest', - [{'YumRepository': {'url': settings.repos.swid_tag.url, 'distro': 'rhel8'}}], - indirect=True, -) @pytest.mark.no_containers +@pytest.mark.rhel_ver_match('8') def test_errata_installation_with_swidtags( - function_repos_collection_with_manifest, rhel8_contenthost, target_sat + module_sca_manifest_org, + rhel_contenthost, + errata_host_lce, + target_sat, ): """Verify errata installation with swid_tags and swid tags get updated after module stream update. :id: 43a59b9a-eb9b-4174-8b8e-73d923b1e51e + :setup: + + 1. rhel8 contenthost checked out, using org with simple content access. + 2. create satellite repositories having rhel8 baseOS, prereqs, custom content w/ swid tags. + 3. associate repositories to org, lifecycle environment, and cv. Sync all content. + 4. publish & promote to environment, content view version with all content. + 5. create activation key, for registering host to cv. + :steps: - 1. create product and repository having swid tags - 2. create content view and published it with repository - 3. create activation key and register content host - 4. create rhel8, swid repos on content host - 5. install swid-tools, dnf-plugin-swidtags packages on content host - 6. install older module stream and generate errata, swid tag - 7. assert errata count, swid tags are generated - 8. install errata vis updating module stream - 9. assert errata count and swid tag after module update + 1. register host using cv's activation key, assert succeeded. + 2. install swid-tools, dnf-plugin-swidtags packages on content host. + 3. install older module stream and generate errata, swid tag. + 4. assert errata count, swid tags are generated. + 5. install errata via updating module stream. + 6. assert errata count and swid tag changed after module update. - :expectedresults: swid tags should get updated after errata installation via - module stream update + :expectedresults: + swid tags should get updated after errata installation via module stream update :CaseAutomation: Automated @@ -1407,44 +1415,100 @@ def test_errata_installation_with_swidtags( """ module_name = 'kangaroo' version = '20180704111719' - # setup base_rhel8 and sat_tools repos - rhel8_contenthost.create_custom_repos( - **{ - 'baseos': settings.repos.rhel8_os.baseos, - 'appstream': settings.repos.rhel8_os.appstream, - } - ) - function_repos_collection_with_manifest.setup_virtual_machine(rhel8_contenthost) + org = module_sca_manifest_org + lce = errata_host_lce + cv = target_sat.api.ContentView( + organization=org, + environment=[lce], + ).create() - # install older module stream - rhel8_contenthost.add_rex_key(satellite=target_sat) - _set_prerequisites_for_swid_repos(rhel8_contenthost) - _run_remote_command_on_content_host( - f'dnf -y module install {module_name}:0:{version}', rhel8_contenthost + repos = { + 'base_os': settings.repos.rhel8_os.baseos, # base rhel8 + 'sat_tools': settings.repos.rhel8_os.appstream, # swid prereqs + 'swid_tags': settings.repos.swid_tag.url, # module stream pkgs and errata + } + # associate repos with sat, org, lce, cv, and sync + for r in repos: + target_sat.cli_factory.setup_org_for_a_custom_repo( + { + 'url': repos[r], + 'organization-id': org.id, + 'lifecycle-environment-id': lce.id, + 'content-view-id': cv.id, + }, + ) + # promote newest cv version with all repos/content + cv = cv_publish_promote( + sat=target_sat, + org=org, + cv=cv, + lce=lce, + )['content-view'] + # ak in env, tied to content-view + ak = target_sat.api.ActivationKey( + organization=org, + environment=lce, + content_view=cv, + ).create() + # register host with ak, succeeds + result = rhel_contenthost.register( + activation_keys=ak.name, + target=target_sat, + org=org, + loc=None, ) - target_sat.cli.Host.errata_recalculate({'host-id': rhel8_contenthost.nailgun_host.id}) + assert result.status == 0, f'Failed to register the host {target_sat.hostname},\n{result}' + assert ( + rhel_contenthost.subscribed + ), f'Failed to subscribe the host {target_sat.hostname}, to content.' + result = rhel_contenthost.execute(r'subscription-manager repos --enable \*') + assert result.status == 0, f'Failed to enable repositories with subscription-manager,\n{result}' + + # install outdated module stream package + _set_prerequisites_for_swid_repos(rhel_contenthost) + result = rhel_contenthost.execute(f'dnf -y module install {module_name}:0:{version}') + assert ( + result.status == 0 + ), f'Failed to install module stream package: {module_name}:0:{version}.\n{result.stdout}' + # recalculate errata after install of old module stream + rhel_contenthost.execute('subscription-manager repos') + # validate swid tags Installed - before_errata_apply_result = _run_remote_command_on_content_host( - f"swidq -i -n {module_name} | grep 'File' | grep -o 'rpm-.*.swidtag'", - rhel8_contenthost, - return_result=True, + result = rhel_contenthost.execute( + f'swidq -i -n {module_name} | grep "File" | grep -o "rpm-.*.swidtag"', + ) + assert ( + result.status == 0 + ), f'An error occured trying to fetch swid tags for {module_name}.\n{result}' + before_errata_apply_result = result.stdout + assert before_errata_apply_result != '', f'Found no swid tags contained in {module_name}.' + assert (app_errata_count := rhel_contenthost.applicable_errata_count) == 1, ( + f'Found {rhel_contenthost.applicable_errata_count} applicable errata,' + f' after installing {module_name}:0:{version}, expected 1.' ) - assert before_errata_apply_result != '' - assert (applicable_errata_count := rhel8_contenthost.applicable_errata_count) == 1 # apply modular errata - _run_remote_command_on_content_host(f'dnf -y module update {module_name}', rhel8_contenthost) - _run_remote_command_on_content_host('dnf -y upload-profile', rhel8_contenthost) - target_sat.cli.Host.errata_recalculate({'host-id': rhel8_contenthost.nailgun_host.id}) - rhel8_contenthost.execute('subscription-manager repos') - applicable_errata_count -= 1 - assert rhel8_contenthost.applicable_errata_count == applicable_errata_count - after_errata_apply_result = _run_remote_command_on_content_host( - f"swidq -i -n {module_name} | grep 'File'| grep -o 'rpm-.*.swidtag'", - rhel8_contenthost, - return_result=True, + result = rhel_contenthost.execute(f'dnf -y module update {module_name}') + assert ( + result.status == 0 + ), f'Failed to update module stream package: {module_name}.\n{result.stdout}' + assert rhel_contenthost.execute('dnf -y upload-profile').status == 0 + + # recalculate and check errata after modular update + rhel_contenthost.execute('subscription-manager repos') + app_errata_count -= 1 + assert rhel_contenthost.applicable_errata_count == app_errata_count, ( + f'Found {rhel_contenthost.applicable_errata_count} applicable errata, after modular update of {module_name},' + f' expected {app_errata_count}.' + ) + # swidtags were updated based on package version + result = rhel_contenthost.execute( + f'swidq -i -n {module_name} | grep "File" | grep -o "rpm-.*.swidtag"', ) - # swidtags get updated based on package version + assert ( + result.status == 0 + ), f'An error occured trying to fetch swid tags for {module_name}.\n{result}' + after_errata_apply_result = result.stdout assert before_errata_apply_result != after_errata_apply_result @@ -1688,7 +1752,7 @@ def test_positive_incremental_update_apply_to_envs_cvs( assert set(FAKE_9_YUM_SECURITY_ERRATUM).issubset(post_app_errata_ids) # expected packages from the security erratum were added to host added_packages = response['output']['changed_content'][0]['added_units']['rpm'] - assert 12 == len(added_packages) + assert len(added_packages) == 12 # expected that not all of the added packages will be applicable assert 8 == host_app_packages == rhel8_contenthost.applicable_package_count # install all of the newly added packages, recalculate applicability diff --git a/tests/foreman/api/test_filter.py b/tests/foreman/api/test_filter.py index ed61c98c76a..d13d5a7ecad 100644 --- a/tests/foreman/api/test_filter.py +++ b/tests/foreman/api/test_filter.py @@ -22,10 +22,9 @@ @pytest.fixture(scope='module') def module_perms(module_target_sat): """Search for provisioning template permissions. Set ``cls.ct_perms``.""" - ct_perms = module_target_sat.api.Permission().search( + return module_target_sat.api.Permission().search( query={'search': 'resource_type="ProvisioningTemplate"'} ) - return ct_perms @pytest.mark.tier1 diff --git a/tests/foreman/api/test_host.py b/tests/foreman/api/test_host.py index 94730d56925..eb3e89dc6aa 100644 --- a/tests/foreman/api/test_host.py +++ b/tests/foreman/api/test_host.py @@ -261,10 +261,10 @@ def test_positive_create_inherit_lce_cv( ).create() host = module_target_sat.api.Host(hostgroup=hostgroup, organization=module_org).create() assert ( - host.content_facet_attributes['lifecycle_environment_id'] + host.content_facet_attributes['lifecycle_environment']['id'] == hostgroup.lifecycle_environment.id ) - assert host.content_facet_attributes['content_view_id'] == hostgroup.content_view.id + assert host.content_facet_attributes['content_view']['id'] == hostgroup.content_view.id @pytest.mark.tier2 @@ -625,16 +625,16 @@ def test_positive_create_and_update_with_content_view( 'lifecycle_environment_id': module_lce_library.id, }, ).create() - assert host.content_facet_attributes['content_view_id'] == module_default_org_view.id - assert host.content_facet_attributes['lifecycle_environment_id'] == module_lce_library.id + assert host.content_facet_attributes['content_view']['id'] == module_default_org_view.id + assert host.content_facet_attributes['lifecycle_environment']['id'] == module_lce_library.id host.content_facet_attributes = { 'content_view_id': module_default_org_view.id, 'lifecycle_environment_id': module_lce_library.id, } host = host.update(['content_facet_attributes']) - assert host.content_facet_attributes['content_view_id'] == module_default_org_view.id - assert host.content_facet_attributes['lifecycle_environment_id'] == module_lce_library.id + assert host.content_facet_attributes['content_view']['id'] == module_default_org_view.id + assert host.content_facet_attributes['lifecycle_environment']['id'] == module_lce_library.id @pytest.mark.tier1 diff --git a/tests/foreman/api/test_hostcollection.py b/tests/foreman/api/test_hostcollection.py index aea239ac1ad..985f989718e 100644 --- a/tests/foreman/api/test_hostcollection.py +++ b/tests/foreman/api/test_hostcollection.py @@ -28,8 +28,7 @@ @pytest.fixture(scope='module') def fake_hosts(module_org, module_target_sat): """Create content hosts that can be shared by tests.""" - hosts = [module_target_sat.api.Host(organization=module_org).create() for _ in range(2)] - return hosts + return [module_target_sat.api.Host(organization=module_org).create() for _ in range(2)] @pytest.mark.parametrize('name', **parametrized(valid_data_list())) diff --git a/tests/foreman/api/test_http_proxy.py b/tests/foreman/api/test_http_proxy.py index 50012a0ab70..1bd00051e67 100644 --- a/tests/foreman/api/test_http_proxy.py +++ b/tests/foreman/api/test_http_proxy.py @@ -303,6 +303,11 @@ def test_positive_sync_proxy_with_certificate(request, target_sat, module_org, m :customerscenario: true """ + + @request.addfinalizer + def _finalize(): + target_sat.custom_certs_cleanup() + # Cleanup any existing certs that may conflict target_sat.custom_certs_cleanup() proxy_host = settings.http_proxy.auth_proxy_url.replace('http://', '').replace(':3128', '') diff --git a/tests/foreman/api/test_ldapauthsource.py b/tests/foreman/api/test_ldapauthsource.py index fbcc7039193..885d290e741 100644 --- a/tests/foreman/api/test_ldapauthsource.py +++ b/tests/foreman/api/test_ldapauthsource.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: LDAP +:CaseComponent: Authentication :Team: Endeavour diff --git a/tests/foreman/api/test_multiple_paths.py b/tests/foreman/api/test_multiple_paths.py index 30531f77025..8c683ca309a 100644 --- a/tests/foreman/api/test_multiple_paths.py +++ b/tests/foreman/api/test_multiple_paths.py @@ -130,7 +130,7 @@ def test_positive_get_status_code(self, entity_cls): logger.info('test_get_status_code arg: %s', entity_cls) response = client.get(entity_cls().path(), auth=get_credentials(), verify=False) response.raise_for_status() - assert http.client.OK == response.status_code + assert response.status_code == http.client.OK assert 'application/json' in response.headers['content-type'] @pytest.mark.tier1 @@ -151,7 +151,7 @@ def test_negative_get_unauthorized(self, entity_cls): """ logger.info('test_get_unauthorized arg: %s', entity_cls) response = client.get(entity_cls().path(), auth=(), verify=False) - assert http.client.UNAUTHORIZED == response.status_code + assert response.status_code == http.client.UNAUTHORIZED @pytest.mark.tier3 @pytest.mark.parametrize( @@ -173,7 +173,7 @@ def test_positive_post_status_code(self, entity_cls): :BZ: 1118015 """ response = entity_cls().create_raw() - assert http.client.CREATED == response.status_code + assert response.status_code == http.client.CREATED assert 'application/json' in response.headers['content-type'] @pytest.mark.tier1 @@ -195,7 +195,7 @@ def test_negative_post_unauthorized(self, entity_cls): """ server_cfg = user_nailgun_config() return_code = entity_cls(server_cfg).create_raw(create_missing=False).status_code - assert http.client.UNAUTHORIZED == return_code + assert return_code == http.client.UNAUTHORIZED class TestEntityId: @@ -217,7 +217,7 @@ def test_positive_get_status_code(self, entity_cls): """ entity = entity_cls(id=entity_cls().create_json()['id']) response = entity.read_raw() - assert http.client.OK == response.status_code + assert response.status_code == http.client.OK assert 'application/json' in response.headers['content-type'] @pytest.mark.tier1 @@ -248,7 +248,7 @@ def test_positive_put_status_code(self, entity_cls): auth=get_credentials(), verify=False, ) - assert http.client.OK == response.status_code + assert response.status_code == http.client.OK assert 'application/json' in response.headers['content-type'] @pytest.mark.tier1 @@ -325,7 +325,7 @@ def test_positive_put_and_get_requests(self, entity_cls): payload = _get_readable_attributes(new_entity) entity_attrs = entity_cls(id=entity['id']).read_json() for key, value in payload.items(): - assert key in entity_attrs.keys() + assert key in entity_attrs assert value == entity_attrs[key] @pytest.mark.tier1 @@ -349,7 +349,7 @@ def test_positive_post_and_get_requests(self, entity_cls): payload = _get_readable_attributes(entity) entity_attrs = entity_cls(id=entity_id).read_json() for key, value in payload.items(): - assert key in entity_attrs.keys() + assert key in entity_attrs assert value == entity_attrs[key] @pytest.mark.tier1 @@ -369,7 +369,7 @@ def test_positive_delete_and_get_requests(self, entity_cls): # Create an entity, delete it and get it. entity = entity_cls(id=entity_cls().create_json()['id']) entity.delete() - assert http.client.NOT_FOUND == entity.read_raw().status_code + assert entity.read_raw().status_code == http.client.NOT_FOUND class TestEntityRead: diff --git a/tests/foreman/api/test_notifications.py b/tests/foreman/api/test_notifications.py index 5ed5c940678..f2477ad3291 100644 --- a/tests/foreman/api/test_notifications.py +++ b/tests/foreman/api/test_notifications.py @@ -43,6 +43,17 @@ def admin_user_with_localhost_email(target_sat): user.delete() +@pytest.fixture +def admin_user_with_custom_settings(request, admin_user_with_localhost_email): + """Admin user with custom properties set via parametrization. + `request.param` should be a dict-like value. + """ + for key, value in request.param.items(): + setattr(admin_user_with_localhost_email, key, value) + admin_user_with_localhost_email.update(list(request.param.keys())) + return admin_user_with_localhost_email + + @pytest.fixture def sysadmin_user_with_subscription_reposync_fail(target_sat): """System admin user with `root@localhost` e-mail @@ -162,6 +173,25 @@ def wait_for_failed_repo_sync_mail( ) +@pytest.fixture +def wait_for_no_long_running_task_mail(target_sat, clean_root_mailbox, long_running_task): + """Wait and check that no long-running task ID is found in the Satellite's mbox file.""" + timeout = 120 + try: + wait_for_mail( + sat_obj=target_sat, + mailbox_file=clean_root_mailbox, + contains_string=long_running_task["task"]["id"], + timeout=timeout, + ) + except AssertionError: + return True + raise AssertionError( + f'E-mail with long running task ID "{long_running_task["task"]["id"]}" ' + f'should not have arrived to mailbox {clean_root_mailbox}!' + ) + + @pytest.fixture def root_mailbox_copy(target_sat, clean_root_mailbox): """Parsed local system copy of the Satellite's root user mailbox. @@ -222,7 +252,7 @@ def long_running_task(target_sat): def fake_yum_repo(target_sat): """Create a fake YUM repo. Delete it afterwards.""" repo = target_sat.api.Repository( - content_type='yum', url=repo_constants.FAKE_YUM_DRPM_REPO + content_type='yum', url=repo_constants.FAKE_YUM_MISSING_REPO ).create() yield repo @@ -333,3 +363,77 @@ def test_positive_notification_failed_repo_sync(failed_repo_sync_task, root_mail body_text = mime_body.as_string() assert product_name in body_text assert f'/foreman_tasks/tasks/{task_id}' in body_text + + +@pytest.mark.tier1 +def test_positive_notification_recipients(target_sat): + """Check that endpoint `/notification_recipients` works and returns correct data structure. + + :id: 10e0fac2-f11f-11ee-ba60-000c2989e153 + + :steps: + 1. Do a GET request to /notification_recipients endpoint. + 2. Check the returned data structure for expected keys. + + :BZ: 2249970 + + :customerscenario: true + """ + notification_keys = [ + 'id', + 'seen', + 'level', + 'text', + 'created_at', + 'group', + 'actions', + ] + + recipients = target_sat.api.NotificationRecipients().read() + for notification in recipients.notifications: + assert set(notification_keys) == set(notification.keys()) + + +@pytest.mark.tier3 +@pytest.mark.parametrize( + 'admin_user_with_custom_settings', + [ + pytest.param({'disabled': True, 'mail_enabled': True}, id='account_disabled'), + pytest.param({'disabled': False, 'mail_enabled': False}, id='mail_disabled'), + ], + indirect=True, +) +@pytest.mark.usefixtures( + 'reschedule_long_running_tasks_notification', + 'wait_for_no_long_running_task_mail', +) +def test_negative_no_notification_for_long_running_tasks( + admin_user_with_custom_settings, long_running_task, root_mailbox_copy +): + """Check that an e-mail notification for a long-running task + (i.e., running or paused for more than two days) + is NOT sent to users with disabled account or disabled e-mail. + + :id: 03b41216-f39b-11ee-b9ea-000c2989e153 + + :setup: + 1. Create an admin user with e-mail address set and: + a. account disabled & mail enabled + b. account enabled & mail disabled + + :steps: + 1. Create a long-running task. + 3. For each user, wait and check that the notification e-mail has NOT been sent. + + :BZ: 2245056 + + :customerscenario: true + """ + assert admin_user_with_custom_settings + task_id = long_running_task['task']['id'] + assert task_id + + for email in root_mailbox_copy: + assert ( + task_id not in email.as_string() + ), f'Unexpected notification e-mail with long-running task ID {task_id} found in user mailbox!' diff --git a/tests/foreman/api/test_organization.py b/tests/foreman/api/test_organization.py index 89988935f98..295bc1142a1 100644 --- a/tests/foreman/api/test_organization.py +++ b/tests/foreman/api/test_organization.py @@ -77,7 +77,7 @@ def test_positive_create(self, target_sat): if is_open('BZ:2228820'): assert response.status_code in [http.client.UNSUPPORTED_MEDIA_TYPE, 500] else: - assert http.client.UNSUPPORTED_MEDIA_TYPE == response.status_code + assert response.status_code == http.client.UNSUPPORTED_MEDIA_TYPE @pytest.mark.tier1 @pytest.mark.build_sanity @@ -132,7 +132,7 @@ def test_negative_create_with_same_name(self, target_sat): target_sat.api.Organization(name=name).create() @pytest.mark.tier1 - def test_negative_check_org_endpoint(self, module_entitlement_manifest_org): + def test_negative_check_org_endpoint(self, module_sca_manifest_org): """Check manifest cert is not exposed in api endpoint :id: 24130e54-cd7a-41de-ac78-6e89aebabe30 @@ -145,7 +145,7 @@ def test_negative_check_org_endpoint(self, module_entitlement_manifest_org): :CaseImportance: High """ - orgstring = json.dumps(module_entitlement_manifest_org.read_json()) + orgstring = json.dumps(module_sca_manifest_org.read_json()) assert 'BEGIN CERTIFICATE' not in orgstring assert 'BEGIN RSA PRIVATE KEY' not in orgstring diff --git a/tests/foreman/api/test_parameters.py b/tests/foreman/api/test_parameters.py index 1e415d15dc8..8e2865fbb93 100644 --- a/tests/foreman/api/test_parameters.py +++ b/tests/foreman/api/test_parameters.py @@ -26,8 +26,8 @@ def test_positive_parameter_precedence_impact( :steps: 1. Create Global Parameter - 2. Create host and verify global parameter is assigned - 3. Create Host Group with parameter + 2. Create Host Group with parameter + 3. Create host and verify global parameter is assigned 4. Assign hostgroup to host created above and verify hostgroup parameter is assigned. 5. Add parameter on the host directly, and verify that this should take precedence over Host group and Global Parameter @@ -39,28 +39,28 @@ def test_positive_parameter_precedence_impact( param_value = gen_string('alpha') cp = module_target_sat.api.CommonParameter(name=param_name, value=param_value).create() - host = module_target_sat.api.Host(organization=module_org, location=module_location).create() - result = [res for res in host.all_parameters if res['name'] == param_name] - assert result[0]['name'] == param_name - assert result[0]['associated_type'] == 'global' + request.addfinalizer(cp.delete) hg = module_target_sat.api.HostGroup( organization=[module_org], group_parameters_attributes=[{'name': param_name, 'value': param_value}], ).create() + request.addfinalizer(hg.delete) + + host = module_target_sat.api.Host(organization=module_org, location=module_location).create() + request.addfinalizer(host.delete) + result = [res for res in host.all_parameters if res['name'] == param_name] + assert result[0]['name'] == param_name + assert result[0]['associated_type'] == 'global' + host.hostgroup = hg + host = host.update(['hostgroup']) result = [res for res in host.all_parameters if res['name'] == param_name] assert result[0]['name'] == param_name assert result[0]['associated_type'] != 'global' assert result[0]['associated_type'] == 'host group' - @request.addfinalizer - def _finalize(): - host.delete() - hg.delete() - cp.delete() - host.host_parameters_attributes = [{'name': param_name, 'value': param_value}] host = host.update(['host_parameters_attributes']) result = [res for res in host.all_parameters if res['name'] == param_name] diff --git a/tests/foreman/api/test_permission.py b/tests/foreman/api/test_permission.py index a4267d71f92..f072d0d734f 100644 --- a/tests/foreman/api/test_permission.py +++ b/tests/foreman/api/test_permission.py @@ -38,28 +38,35 @@ def create_permissions(self, class_target_sat): # workaround for setting class variables cls = type(self) cls.permissions = PERMISSIONS.copy() - if class_target_sat.is_upstream: - cls.permissions[None].extend(cls.permissions.pop('DiscoveryRule')) - cls.permissions[None].remove('app_root') - cls.permissions[None].remove('attachments') - cls.permissions[None].remove('configuration') - cls.permissions[None].remove('logs') - cls.permissions[None].remove('view_cases') - cls.permissions[None].remove('view_log_viewer') - - result = class_target_sat.execute('rpm -qa | grep rubygem-foreman_openscap') - if result.status != 0: + + rpm_packages = class_target_sat.execute('rpm -qa').stdout + if 'rubygem-foreman_rh_cloud' not in rpm_packages: + cls.permissions.pop('InsightsHit') + cls.permissions[None].remove('generate_foreman_rh_cloud') + cls.permissions[None].remove('view_foreman_rh_cloud') + cls.permissions[None].remove('dispatch_cloud_requests') + cls.permissions[None].remove('control_organization_insights') + if 'rubygem-foreman_bootdisk' not in rpm_packages: + cls.permissions[None].remove('download_bootdisk') + if 'rubygem-foreman_virt_who_configure' not in rpm_packages: + cls.permissions.pop('ForemanVirtWhoConfigure::Config') + if 'rubygem-foreman_openscap' not in rpm_packages: cls.permissions.pop('ForemanOpenscap::Policy') cls.permissions.pop('ForemanOpenscap::ScapContent') cls.permissions[None].remove('destroy_arf_reports') cls.permissions[None].remove('view_arf_reports') cls.permissions[None].remove('create_arf_reports') - result = class_target_sat.execute('rpm -qa | grep rubygem-foreman_remote_execution') - if result.status != 0: + if 'rubygem-foreman_remote_execution' not in rpm_packages: cls.permissions.pop('JobInvocation') cls.permissions.pop('JobTemplate') cls.permissions.pop('RemoteExecutionFeature') cls.permissions.pop('TemplateInvocation') + if 'rubygem-foreman_puppet' not in rpm_packages: + cls.permissions.pop('ForemanPuppet::ConfigGroup') + cls.permissions.pop('ForemanPuppet::Environment') + cls.permissions.pop('ForemanPuppet::HostClass') + cls.permissions.pop('ForemanPuppet::Puppetclass') + cls.permissions.pop('ForemanPuppet::PuppetclassLookupKey') #: e.g. ['Architecture', 'Audit', 'AuthSourceLdap', …] cls.permission_resource_types = list(cls.permissions.keys()) @@ -161,18 +168,20 @@ def test_positive_search(self, target_sat): # FIXME: This method is a hack. This information should somehow be tied # directly to the `Entity` classes. -def _permission_name(entity, which_perm): - """Find a permission name. +def _permission_names(entity, which_perm: str) -> list: + """Find permission names. + + Attempt to locate permissions in :data:`robottelo.constants.PERMISSIONS`. - Attempt to locate a permission in :data:`robottelo.constants.PERMISSIONS`. - For example, return 'view_architectures' if ``entity`` is ``Architecture`` - and ``which_perm`` is 'read'. + Examples: + _permission_names('Architecture', 'read') -> ['view_architectures'] + _permission_names('Host', 'delete') -> ['destroy_discovered_hosts', 'destroy_hosts'] :param entity: A ``nailgun.entity_mixins.Entity`` subclass. :param str which_perm: Either the word "create", "read", "update" or "delete". - :raise: ``LookupError`` if a relevant permission cannot be found, or if - multiple results are found. + :raise: ``LookupError`` if a relevant permission cannot be found + :returns: list of found permission names """ pattern = {'create': '^create_', 'delete': '^destroy_', 'read': '^view_', 'update': '^edit_'}[ which_perm @@ -181,11 +190,11 @@ def _permission_name(entity, which_perm): permissions = PERMISSIONS.get(entity.__name__) or PERMISSIONS.get(f'Katello::{entity.__name__}') for permission in permissions: match = re.match(pattern, permission) - if match is not None: + if match: perm_names.append(permission) - if len(perm_names) != 1: - raise LookupError(f'Could not find the requested permission. Found: {perm_names}') - return perm_names[0] + if not perm_names: + raise LookupError(f'Could not find any "{which_perm}" permission for entity "{entity}"') + return perm_names # This class might better belong in module test_multiple_paths. @@ -226,6 +235,15 @@ def give_user_permission(self, perm_name, target_sat): self.user.role += [role] self.user = self.user.update(['role']) + def give_user_permissions(self, perm_names, target_sat): + """Give ``self.user`` multiple permissions. + Otherwise, works the same as method ``self.give_user_permission``. + + :param list perm_names: permission names + """ + for perm_name in perm_names: + self.give_user_permission(perm_name, target_sat) + def set_taxonomies(self, entity, organization=None, location=None): """Set organization and location for entity if it supports them. @@ -273,7 +291,7 @@ def test_positive_check_create(self, entity_cls, class_org, class_location, targ """ with pytest.raises(HTTPError): entity_cls(self.cfg).create() - self.give_user_permission(_permission_name(entity_cls, 'create'), target_sat) + self.give_user_permissions(_permission_names(entity_cls, 'create'), target_sat) new_entity = self.set_taxonomies(entity_cls(self.cfg), class_org, class_location) # Entities with both org and loc require # additional permissions to set them. @@ -307,14 +325,16 @@ def test_positive_check_read(self, entity_cls, class_org, class_location, target new_entity = new_entity.create() with pytest.raises(HTTPError): entity_cls(self.cfg, id=new_entity.id).read() - self.give_user_permission(_permission_name(entity_cls, 'read'), target_sat) + self.give_user_permissions(_permission_names(entity_cls, 'read'), target_sat) entity_cls(self.cfg, id=new_entity.id).read() @pytest.mark.upgrade @pytest.mark.tier1 @pytest.mark.parametrize( 'entity_cls', - **parametrized([entities.Architecture, entities.Domain, entities.ActivationKey]), + **parametrized( + [entities.Architecture, entities.Domain, entities.ActivationKey, entities.Host] + ), ) def test_positive_check_delete(self, entity_cls, class_org, class_location, target_sat): """Check whether the "destroy_*" role has an effect. @@ -334,7 +354,7 @@ def test_positive_check_delete(self, entity_cls, class_org, class_location, targ new_entity = new_entity.create() with pytest.raises(HTTPError): entity_cls(self.cfg, id=new_entity.id).delete() - self.give_user_permission(_permission_name(entity_cls, 'delete'), target_sat) + self.give_user_permissions(_permission_names(entity_cls, 'delete'), target_sat) entity_cls(self.cfg, id=new_entity.id).delete() with pytest.raises(HTTPError): new_entity.read() # As admin user @@ -371,7 +391,7 @@ def test_positive_check_update(self, entity_cls, class_org, class_location, targ update_entity = entity_cls(self.cfg, id=new_entity.id, name=name) with pytest.raises(HTTPError): update_entity.update(['name']) - self.give_user_permission(_permission_name(entity_cls, 'update'), target_sat) + self.give_user_permissions(_permission_names(entity_cls, 'update'), target_sat) # update() calls read() under the hood, which triggers # permission error if entity_cls is entities.ActivationKey: diff --git a/tests/foreman/api/test_provisioningtemplate.py b/tests/foreman/api/test_provisioningtemplate.py index 95f0b3883f0..52e5fc2f66d 100644 --- a/tests/foreman/api/test_provisioningtemplate.py +++ b/tests/foreman/api/test_provisioningtemplate.py @@ -120,7 +120,7 @@ def tftpboot(module_org, module_target_sat): for setting in default_settings: if setting.value is None: setting.value = '' - setting.update(fields=['value'] or '') + setting.update(fields=['value']) class TestProvisioningTemplate: @@ -629,7 +629,7 @@ def test_positive_template_check_rex_snippet( assert f'chown -R {rex_user}: ~{rex_user}' in rex_snippet assert f'chown -R {rex_user}: ~{rex_user}/.ssh' in rex_snippet assert ( - f'echo "{rex_user} ALL = (root) NOPASSWD : ALL\nDefaults:{rex_user} !requiretty" > /etc/sudoers.d/{rex_user}' + f'echo "{rex_user} ALL = (root) NOPASSWD : ALL" > /etc/sudoers.d/{rex_user}\necho "Defaults:{rex_user} !requiretty" >> /etc/sudoers.d/{rex_user}' in rex_snippet ) assert ssh_key in rex_snippet diff --git a/tests/foreman/api/test_registration.py b/tests/foreman/api/test_registration.py index 606dbae9dca..04b23933309 100644 --- a/tests/foreman/api/test_registration.py +++ b/tests/foreman/api/test_registration.py @@ -62,7 +62,7 @@ def test_host_registration_end_to_end( # Verify server.hostname and server.port from subscription-manager config assert module_target_sat.hostname == rhel_contenthost.subscription_config['server']['hostname'] - assert constants.CLIENT_PORT == rhel_contenthost.subscription_config['server']['port'] + assert rhel_contenthost.subscription_config['server']['port'] == constants.CLIENT_PORT # Update module_capsule_configured to include module_org/module_location nc = module_capsule_configured.nailgun_smart_proxy @@ -86,7 +86,7 @@ def test_host_registration_end_to_end( module_capsule_configured.hostname == rhel_contenthost.subscription_config['server']['hostname'] ) - assert constants.CLIENT_PORT == rhel_contenthost.subscription_config['server']['port'] + assert rhel_contenthost.subscription_config['server']['port'] == constants.CLIENT_PORT @pytest.mark.tier3 @@ -227,7 +227,7 @@ def test_negative_capsule_without_registration_enabled( module_target_sat, module_capsule_configured, module_ak_with_cv, - module_entitlement_manifest_org, + module_sca_manifest_org, module_location, ): """Verify registration with Capsule, when registration isn't configured in installer @@ -241,7 +241,7 @@ def test_negative_capsule_without_registration_enabled( :expectedresults: Registration fails with HTTP error code 422 and an error message. """ - org = module_entitlement_manifest_org + org = module_sca_manifest_org nc = module_capsule_configured.nailgun_smart_proxy module_target_sat.api.SmartProxy(id=nc.id, organization=[org]).update(['organization']) diff --git a/tests/foreman/api/test_repositories.py b/tests/foreman/api/test_repositories.py index 9c577dd7349..68dc13b0657 100644 --- a/tests/foreman/api/test_repositories.py +++ b/tests/foreman/api/test_repositories.py @@ -18,7 +18,12 @@ from robottelo import constants from robottelo.config import settings -from robottelo.constants import DEFAULT_ARCHITECTURE, MIRRORING_POLICIES, REPOS +from robottelo.constants import ( + DEFAULT_ARCHITECTURE, + MIRRORING_POLICIES, + REPOS, +) +from robottelo.constants.repos import FAKE_ZST_REPO from robottelo.exceptions import CLIReturnCodeError from robottelo.utils.datafactory import parametrized @@ -186,6 +191,39 @@ def test_positive_sync_kickstart_repo(module_sca_manifest_org, target_sat): assert rh_repo.content_counts['rpm'] > 0 +def test_positive_sync_upstream_repo_with_zst_compression( + module_org, module_product, module_target_sat +): + """Sync upstream repo having zst compression and verify it succeeds. + + :id: 1eddff2a-b6b5-420b-a0e8-ba6a05c11ca4 + + :expectedresults: Repo sync is successful and no zst type compression errors are present in /var/log/messages. + + :steps: + + 1. Sync upstream repository having zst type compression. + 2. Assert that no errors related to compression type are present in + /var/log/messages. + 3. Assert that sync was executed properly. + + :BZ: 2241934 + + :customerscenario: true + """ + repo = module_target_sat.api.Repository( + product=module_product, content_type='yum', url=FAKE_ZST_REPO + ).create() + assert repo.read().content_counts['rpm'] == 0 + sync = module_product.sync() + assert sync['result'] == 'success' + assert repo.read().content_counts['rpm'] > 0 + result = module_target_sat.execute( + 'grep pulp /var/log/messages | grep "Cannot detect compression type"' + ) + assert result.status == 1 + + @pytest.mark.tier1 def test_negative_upload_expired_manifest(module_org, target_sat): """Upload an expired manifest and attempt to refresh it diff --git a/tests/foreman/api/test_repository.py b/tests/foreman/api/test_repository.py index 897c27d5f53..e253b9c957f 100644 --- a/tests/foreman/api/test_repository.py +++ b/tests/foreman/api/test_repository.py @@ -50,12 +50,6 @@ def repo_options_custom_product(request, module_org, module_target_sat): return options -@pytest.fixture -def env(module_org, module_target_sat): - """Create a new puppet environment.""" - return module_target_sat.api.Environment(organization=[module_org]).create() - - @pytest.fixture def repo(repo_options, module_target_sat): """Create a new repository.""" @@ -485,14 +479,13 @@ def test_negative_update_to_invalid_download_policy(self, repo, target_sat): @pytest.mark.tier1 @pytest.mark.parametrize( 'repo_options', - **datafactory.parametrized( - [ - {'content_type': content_type, 'download_policy': 'on_demand'} - for content_type in constants.REPO_TYPE.keys() - if content_type != 'yum' - ] - ), + [ + {'content_type': content_type, 'download_policy': 'on_demand'} + for content_type in constants.REPO_TYPE + if content_type not in ['yum', 'docker'] + ], indirect=True, + ids=lambda x: x['content_type'], ) def test_negative_create_non_yum_with_download_policy(self, repo_options, target_sat): """Verify that non-YUM repositories cannot be created with @@ -1122,7 +1115,7 @@ def test_module_stream_repository_crud_operations(self, repo): with pytest.raises(HTTPError): repo.read() - def test_positive_recreate_pulp_repositories(self, module_entitlement_manifest_org, target_sat): + def test_positive_recreate_pulp_repositories(self, module_sca_manifest_org, target_sat): """Verify that deleted Pulp Repositories can be recreated using the command 'foreman-rake katello:correct_repositories COMMIT=true' @@ -1137,7 +1130,7 @@ def test_positive_recreate_pulp_repositories(self, module_entitlement_manifest_o """ repo_id = target_sat.api_factory.enable_rhrepo_and_fetchid( basearch='x86_64', - org_id=module_entitlement_manifest_org.id, + org_id=module_sca_manifest_org.id, product=constants.PRDS['rhel'], repo=constants.REPOS['rhst7']['name'], reposet=constants.REPOSET['rhst7'], @@ -1435,9 +1428,7 @@ def test_positive_sync_sha_repo(self, repo, target_sat): assert result.status == 1 @pytest.mark.tier2 - def test_positive_sync_repo_null_contents_changed( - self, module_entitlement_manifest_org, target_sat - ): + def test_positive_sync_repo_null_contents_changed(self, module_sca_manifest_org, target_sat): """test for null contents_changed parameter on actions::katello::repository::sync. :id: f3923940-e097-4da3-aba7-b14dbcda857b @@ -1455,7 +1446,7 @@ def test_positive_sync_repo_null_contents_changed( """ repo_id = target_sat.api_factory.enable_rhrepo_and_fetchid( basearch='x86_64', - org_id=module_entitlement_manifest_org.id, + org_id=module_sca_manifest_org.id, product=constants.PRDS['rhel'], repo=constants.REPOS['rhst7']['name'], reposet=constants.REPOSET['rhst7'], @@ -1478,9 +1469,7 @@ def test_positive_sync_repo_null_contents_changed( if isinstance(ver, int) ], ) - def test_positive_sync_kickstart_check_os( - self, module_entitlement_manifest_org, distro, target_sat - ): + def test_positive_sync_kickstart_check_os(self, module_sca_manifest_org, distro, target_sat): """Sync rhel KS repo and assert that OS was created :id: f84bcf1b-717e-40e7-82ee-000eead45249 @@ -1495,10 +1484,10 @@ def test_positive_sync_kickstart_check_os( 1. OS with corresponding version was created. """ - distro = f'rhel{distro} + "_bos"' if distro > 7 else f'rhel{distro}' + distro = f'rhel{distro}_bos' if distro > 7 else f'rhel{distro}' repo_id = target_sat.api_factory.enable_rhrepo_and_fetchid( basearch='x86_64', - org_id=module_entitlement_manifest_org.id, + org_id=module_sca_manifest_org.id, product=constants.REPOS['kickstart'][distro]['product'], reposet=constants.REPOSET['kickstart'][distro], repo=constants.REPOS['kickstart'][distro]['name'], @@ -2099,7 +2088,7 @@ class TestSRPMRepository: @pytest.mark.upgrade @pytest.mark.tier2 def test_positive_srpm_upload_publish_promote_cv( - self, module_org, env, repo, module_target_sat + self, module_org, module_lce, repo, module_target_sat ): """Upload SRPM to repository, add repository to content view and publish, promote content view @@ -2133,7 +2122,6 @@ def test_positive_srpm_upload_publish_promote_cv( @pytest.mark.upgrade @pytest.mark.tier2 - @pytest.mark.skip('Uses deprecated SRPM repository') @pytest.mark.skipif( (not settings.robottelo.REPOS_HOSTING_URL), reason='Missing repos_hosting_url' ) @@ -2142,7 +2130,7 @@ def test_positive_srpm_upload_publish_promote_cv( **datafactory.parametrized({'fake_srpm': {'url': repo_constants.FAKE_YUM_SRPM_REPO}}), indirect=True, ) - def test_positive_repo_sync_publish_promote_cv(self, module_org, env, repo, target_sat): + def test_positive_repo_sync_publish_promote_cv(self, module_org, module_lce, repo, target_sat): """Synchronize repository with SRPMs, add repository to content view and publish, promote content view @@ -2166,8 +2154,8 @@ def test_positive_repo_sync_publish_promote_cv(self, module_org, env, repo, targ >= 3 ) - cv.version[0].promote(data={'environment_ids': env.id, 'force': False}) - assert len(target_sat.api.Srpms().search(query={'environment_id': env.id})) == 3 + cv.version[0].promote(data={'environment_ids': module_lce.id, 'force': False}) + assert len(target_sat.api.Srpms().search(query={'environment_id': module_lce.id})) >= 3 class TestSRPMRepositoryIgnoreContent: @@ -2295,13 +2283,14 @@ def test_positive_upload_file_to_file_repo(self, repo, target_sat): :CaseAutomation: Automated """ - repo.upload_content(files={'content': DataFile.RPM_TO_UPLOAD.read_bytes()}) + with open(DataFile.FAKE_FILE_NEW_NAME, 'rb') as handle: + repo.upload_content(files={'content': handle}) assert repo.read().content_counts['file'] == 1 filesearch = target_sat.api.File().search( - query={"search": f"name={constants.RPM_TO_UPLOAD}"} + query={"search": f"name={constants.FAKE_FILE_NEW_NAME}"} ) - assert constants.RPM_TO_UPLOAD == filesearch[0].name + assert filesearch[0].name == constants.FAKE_FILE_NEW_NAME @pytest.mark.tier1 @pytest.mark.upgrade diff --git a/tests/foreman/api/test_rhc.py b/tests/foreman/api/test_rhc.py index 521b1cb15da..391277bfb38 100644 --- a/tests/foreman/api/test_rhc.py +++ b/tests/foreman/api/test_rhc.py @@ -4,9 +4,9 @@ :CaseAutomation: Automated -:CaseComponent: RHCloud-CloudConnector +:CaseComponent: RHCloud -:Team: Platform +:Team: Phoenix-subscriptions :CaseImportance: High diff --git a/tests/foreman/api/test_rhcloud_inventory.py b/tests/foreman/api/test_rhcloud_inventory.py index 9cbd70dc2d4..2eb17f408f3 100644 --- a/tests/foreman/api/test_rhcloud_inventory.py +++ b/tests/foreman/api/test_rhcloud_inventory.py @@ -1,12 +1,12 @@ """API tests for RH Cloud - Inventory, also known as Insights Inventory Upload -:Requirement: RH Cloud - Inventory +:Requirement: RHCloud :CaseAutomation: Automated -:CaseComponent: RHCloud-Inventory +:CaseComponent: RHCloud -:Team: Platform +:Team: Phoenix-subscriptions :CaseImportance: High diff --git a/tests/foreman/api/test_role.py b/tests/foreman/api/test_role.py index 1ce1014f269..7d5a12bc591 100644 --- a/tests/foreman/api/test_role.py +++ b/tests/foreman/api/test_role.py @@ -72,7 +72,7 @@ def create_org_admin_role(self, target_sat, name=None, orgs=None, locs=None): :return dict: This function returns dict representation of cloned role data returned from 'clone' function """ - name = gen_string('alpha') if not name else name + name = name if name else gen_string('alpha') default_org_admin = target_sat.api.Role().search( query={'search': 'name="Organization admin"'} ) diff --git a/tests/foreman/api/test_subnet.py b/tests/foreman/api/test_subnet.py index b4882116f8e..99873b537f5 100644 --- a/tests/foreman/api/test_subnet.py +++ b/tests/foreman/api/test_subnet.py @@ -336,9 +336,8 @@ def test_negative_update_parameter(new_name, target_sat): sub_param.update(['name']) -@pytest.mark.stubbed @pytest.mark.tier2 -def test_positive_update_subnet_parameter_host_impact(): +def test_positive_update_subnet_parameter_host_impact(target_sat): """Update in parameter name and value from subnet component updates the parameter in host inheriting that subnet @@ -353,12 +352,29 @@ def test_positive_update_subnet_parameter_host_impact(): :expectedresults: 1. The inherited subnet parameter in host should have - updated name and value - 2. The inherited subnet parameter in host enc should have - updated name and value + updated name and value. :BZ: 1470014 """ + parameter = [{'name': gen_string('alpha'), 'value': gen_string('alpha')}] + org = target_sat.api.Organization().create() + loc = target_sat.api.Location(organization=[org]).create() + org_subnet = target_sat.api.Subnet( + location=[loc], organization=[org], subnet_parameters_attributes=parameter + ).create() + assert parameter[0]['name'] == org_subnet.subnet_parameters_attributes[0]['name'] + assert parameter[0]['value'] == org_subnet.subnet_parameters_attributes[0]['value'] + host = target_sat.api.Host(location=loc, organization=org, subnet=org_subnet).create() + parameter_new_value = [{'name': gen_string('alpha'), 'value': gen_string('alpha')}] + org_subnet.subnet_parameters_attributes = parameter_new_value + org_subnet.update(['subnet_parameters_attributes']) + assert ( + host.subnet.read().subnet_parameters_attributes[0]['name'] == parameter_new_value[0]['name'] + ) + assert ( + host.subnet.read().subnet_parameters_attributes[0]['value'] + == parameter_new_value[0]['value'] + ) @pytest.mark.tier1 diff --git a/tests/foreman/api/test_subscription.py b/tests/foreman/api/test_subscription.py index 316ef4e7448..ec122ec6bfb 100644 --- a/tests/foreman/api/test_subscription.py +++ b/tests/foreman/api/test_subscription.py @@ -54,7 +54,7 @@ def custom_repo(rh_repo, module_sca_manifest_org, module_target_sat): @pytest.fixture(scope='module') def module_ak(module_sca_manifest_org, rh_repo, custom_repo, module_target_sat): """rh_repo and custom_repo are included here to ensure their execution before the AK""" - module_ak = module_target_sat.api.ActivationKey( + return module_target_sat.api.ActivationKey( content_view=module_sca_manifest_org.default_content_view, max_hosts=100, organization=module_sca_manifest_org, @@ -63,7 +63,6 @@ def module_ak(module_sca_manifest_org, rh_repo, custom_repo, module_target_sat): ), auto_attach=True, ).create() - return module_ak @pytest.mark.tier1 @@ -443,7 +442,7 @@ def test_positive_os_restriction_on_repos(): """ -def test_positive_async_endpoint_for_manifest_refresh(target_sat, module_entitlement_manifest_org): +def test_positive_async_endpoint_for_manifest_refresh(target_sat, function_sca_manifest_org): """Verify that manifest refresh is using an async endpoint. Previously this was a single, synchronous endpoint. The endpoint to retrieve manifests is now split into two: an async endpoint to start "exporting" the manifest, and a second endpoint to download the @@ -462,12 +461,12 @@ def test_positive_async_endpoint_for_manifest_refresh(target_sat, module_entitle :BZ: 2066323 """ - sub = target_sat.api.Subscription(organization=module_entitlement_manifest_org) + sub = target_sat.api.Subscription(organization=function_sca_manifest_org) # set log level to 'debug' and restart services target_sat.cli.Admin.logging({'all': True, 'level-debug': True}) target_sat.cli.Service.restart() # refresh manifest and assert new log message to confirm async endpoint - sub.refresh_manifest(data={'organization_id': module_entitlement_manifest_org.id}) + sub.refresh_manifest(data={'organization_id': function_sca_manifest_org.id}) results = target_sat.execute( 'grep "Sending GET request to upstream Candlepin" /var/log/foreman/production.log' ) diff --git a/tests/foreman/api/test_syncplan.py b/tests/foreman/api/test_syncplan.py index 3ecbfac8714..860d2e1486b 100644 --- a/tests/foreman/api/test_syncplan.py +++ b/tests/foreman/api/test_syncplan.py @@ -783,7 +783,7 @@ def test_positive_synchronize_custom_products_future_sync_date(module_org, reque @pytest.mark.run_in_one_thread @pytest.mark.tier4 def test_positive_synchronize_rh_product_past_sync_date( - request, function_entitlement_manifest_org, target_sat + request, function_sca_manifest_org, target_sat ): """Create a sync plan with past datetime as a sync date, add a RH product and verify the product gets synchronized on the next sync @@ -799,7 +799,7 @@ def test_positive_synchronize_rh_product_past_sync_date( """ interval = 60 * 60 # 'hourly' sync interval in seconds delay = 2 * 60 - org = function_entitlement_manifest_org + org = function_sca_manifest_org repo_id = target_sat.api_factory.enable_rhrepo_and_fetchid( basearch='x86_64', org_id=org.id, @@ -851,7 +851,7 @@ def test_positive_synchronize_rh_product_past_sync_date( @pytest.mark.tier4 @pytest.mark.upgrade def test_positive_synchronize_rh_product_future_sync_date( - request, function_entitlement_manifest_org, target_sat + request, function_sca_manifest_org, target_sat ): """Create a sync plan with sync date in a future and sync one RH product with it automatically. @@ -861,7 +861,7 @@ def test_positive_synchronize_rh_product_future_sync_date( :expectedresults: Product is synchronized successfully. """ delay = 2 * 60 # delay for sync date in seconds - org = function_entitlement_manifest_org + org = function_sca_manifest_org repo_id = target_sat.api_factory.enable_rhrepo_and_fetchid( basearch='x86_64', org_id=org.id, diff --git a/tests/foreman/api/test_template_combination.py b/tests/foreman/api/test_template_combination.py index a10125e0a54..7e9d5f11be8 100644 --- a/tests/foreman/api/test_template_combination.py +++ b/tests/foreman/api/test_template_combination.py @@ -49,10 +49,10 @@ def test_positive_end_to_end_template_combination(request, module_target_sat, mo assert module_hostgroup.id == combination.hostgroup.id # DELETE - assert 1 == len(template.read().template_combinations) + assert len(template.read().template_combinations) == 1 combination.delete() with pytest.raises(HTTPError): combination.read() - assert 0 == len(template.read().template_combinations) + assert len(template.read().template_combinations) == 0 template.delete() module_hostgroup.delete() diff --git a/tests/foreman/api/test_templatesync.py b/tests/foreman/api/test_templatesync.py index d91c8ad378d..bda401ef373 100644 --- a/tests/foreman/api/test_templatesync.py +++ b/tests/foreman/api/test_templatesync.py @@ -693,7 +693,7 @@ def test_positive_import_json_output_name_key( template = target_sat.api.Template().imports( data={'repo': dir_path, 'organization_ids': [module_org.id]} ) - assert 'name' in template['message']['templates'][0].keys() + assert 'name' in template['message']['templates'][0] assert template_name == template['message']['templates'][0]['name'] @pytest.mark.tier2 @@ -748,7 +748,7 @@ def test_positive_import_json_output_file_key( template = module_target_sat.api.Template().imports( data={'repo': dir_path, 'organization_ids': [module_org.id]} ) - assert 'example_template.erb' == template['message']['templates'][0]['file'] + assert template['message']['templates'][0]['file'] == 'example_template.erb' @pytest.mark.tier2 def test_positive_import_json_output_corrupted_metadata( @@ -780,7 +780,7 @@ def test_positive_import_json_output_corrupted_metadata( ) assert not bool(template['message']['templates'][0]['imported']) assert ( - 'Failed to parse metadata' == template['message']['templates'][0]['additional_errors'] + template['message']['templates'][0]['additional_errors'] == 'Failed to parse metadata' ) @pytest.mark.skip_if_open('BZ:1787355') @@ -816,8 +816,8 @@ def test_positive_import_json_output_filtered_skip_message( ) assert not bool(template['message']['templates'][0]['imported']) assert ( - "Skipping, 'name' filtered out based on 'filter' and 'negate' settings" - == template['message']['templates'][0]['additional_info'] + template['message']['templates'][0]['additional_info'] + == "Skipping, 'name' filtered out based on 'filter' and 'negate' settings" ) @pytest.mark.tier2 @@ -850,8 +850,8 @@ def test_positive_import_json_output_no_name_error( ) assert not bool(template['message']['templates'][0]['imported']) assert ( - "No 'name' found in metadata" - == template['message']['templates'][0]['additional_errors'] + template['message']['templates'][0]['additional_errors'] + == "No 'name' found in metadata" ) @pytest.mark.tier2 @@ -884,8 +884,8 @@ def test_positive_import_json_output_no_model_error( ) assert not bool(template['message']['templates'][0]['imported']) assert ( - "No 'model' found in metadata" - == template['message']['templates'][0]['additional_errors'] + template['message']['templates'][0]['additional_errors'] + == "No 'model' found in metadata" ) @pytest.mark.tier2 @@ -918,8 +918,8 @@ def test_positive_import_json_output_blank_model_error( ) assert not bool(template['message']['templates'][0]['imported']) assert ( - "Template type was not found, are you missing a plugin?" - == template['message']['templates'][0]['additional_errors'] + template['message']['templates'][0]['additional_errors'] + == "Template type was not found, are you missing a plugin?" ) @pytest.mark.tier2 @@ -965,7 +965,7 @@ def test_positive_export_json_output( template['exported'] for template in exported_templates['message']['templates'] ].count(True) assert exported_count == 17 - assert 'name' in exported_templates['message']['templates'][0].keys() + assert 'name' in exported_templates['message']['templates'][0] assert ( target_sat.execute( f'[ -d {dir_path}/job_templates ] && ' diff --git a/tests/foreman/cli/test_activationkey.py b/tests/foreman/cli/test_activationkey.py index 68c43807fae..b4a0616ed4f 100644 --- a/tests/foreman/cli/test_activationkey.py +++ b/tests/foreman/cli/test_activationkey.py @@ -257,9 +257,8 @@ def test_negative_create_with_usage_limit_with_not_integers(module_org, limit, m module_target_sat.cli_factory.make_activation_key( {'organization-id': module_org.id, 'max-hosts': limit} ) - if isinstance(limit, int): - if limit < 1: - assert 'Max hosts cannot be less than one' in str(raise_ctx) + if isinstance(limit, int) and limit < 1: + assert 'Max hosts cannot be less than one' in str(raise_ctx) if isinstance(limit, str): assert 'Numeric value is required.' in str(raise_ctx) @@ -1404,7 +1403,7 @@ def test_positive_update_autoattach(module_org, module_target_sat): result = module_target_sat.cli.ActivationKey.update( {'auto-attach': new_value, 'id': new_ak['id'], 'organization-id': module_org.id} ) - assert 'Activation key updated.' == result[0]['message'] + assert result[0]['message'] == 'Activation key updated.' @pytest.mark.tier2 diff --git a/tests/foreman/cli/test_ansible.py b/tests/foreman/cli/test_ansible.py index b628c9d8fa2..707acbcfec9 100644 --- a/tests/foreman/cli/test_ansible.py +++ b/tests/foreman/cli/test_ansible.py @@ -4,160 +4,520 @@ :CaseAutomation: Automated -:CaseComponent: Ansible - :Team: Rocket :CaseImportance: High - """ +from time import sleep + from fauxfactory import gen_string import pytest from robottelo.config import settings -@pytest.mark.e2e -@pytest.mark.no_containers -@pytest.mark.rhel_ver_match('[^6].*') -def test_positive_ansible_e2e(target_sat, module_org, rhel_contenthost): - """ - Test successful execution of Ansible Job on host. - - :id: 0c52bc63-a41a-4f48-a980-fe49b4ecdbdc - - :steps: - 1. Register a content host with satellite - 2. Import a role into satellite - 3. Assign that role to a host - 4. Assert that the role and variable were assigned to the host successfully - 5. Run the Ansible playbook associated with that role - 6. Check if the job is executed successfully. - 7. Disassociate the Role from the host. - 8. Delete the assigned ansible role - - :expectedresults: - 1. Host should be assigned the proper role. - 2. Job execution must be successful. - 3. Operations performed with hammer must be successful. - - :CaseAutomation: Automated - - :BZ: 2154184 - - :customerscenario: true - - :CaseImportance: Critical +def assert_job_invocation_result( + sat, invocation_command_id, client_hostname, expected_result='success' +): + """Asserts the job invocation finished with the expected result and fetches job output + when error occurs. Result is one of: success, pending, error, warning""" + result = sat.cli.JobInvocation.info({'id': invocation_command_id}) + try: + assert result[expected_result] == '1' + except AssertionError as err: + raise AssertionError( + 'host output: {}'.format( + ' '.join( + sat.cli.JobInvocation.get_output( + {'id': invocation_command_id, 'host': client_hostname} + ) + ) + ) + ) from err + + +@pytest.mark.upgrade +class TestAnsibleCfgMgmt: + """Test class for Configuration Management with Ansible + + :CaseComponent: Ansible-ConfigurationManagement """ - SELECTED_ROLE = 'RedHatInsights.insights-client' - SELECTED_ROLE_1 = 'theforeman.foreman_scap_client' - SELECTED_VAR = gen_string('alpha') - # disable batch tasks to test BZ#2154184 - target_sat.cli.Settings.set({'name': "foreman_tasks_proxy_batch_trigger", 'value': "false"}) - if rhel_contenthost.os_version.major <= 7: - rhel_contenthost.create_custom_repos(rhel7=settings.repos.rhel7_os) - assert rhel_contenthost.execute('yum install -y insights-client').status == 0 - rhel_contenthost.install_katello_ca(target_sat) - rhel_contenthost.register_contenthost(module_org.label, force=True) - assert rhel_contenthost.subscribed - rhel_contenthost.add_rex_key(satellite=target_sat) - proxy_id = target_sat.nailgun_smart_proxy.id - target_host = rhel_contenthost.nailgun_host - - target_sat.cli.Ansible.roles_sync( - {'role-names': f'{SELECTED_ROLE},{SELECTED_ROLE_1}', 'proxy-id': proxy_id} - ) - - result = target_sat.cli.Host.ansible_roles_add( - {'id': target_host.id, 'ansible-role': SELECTED_ROLE} - ) - assert 'Ansible role has been associated.' in result[0]['message'] - - target_sat.cli.Ansible.variables_create( - {'variable': SELECTED_VAR, 'ansible-role': SELECTED_ROLE} - ) - - assert SELECTED_ROLE, ( - SELECTED_VAR in target_sat.cli.Ansible.variables_info({'name': SELECTED_VAR}).stdout - ) - template_id = ( - target_sat.api.JobTemplate() - .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] - .id - ) - job = target_sat.api.JobInvocation().run( - synchronous=False, - data={ - 'job_template_id': template_id, - 'targeting_type': 'static_query', - 'search_query': f'name = {rhel_contenthost.hostname}', - }, - ) - target_sat.wait_for_tasks( - f'resource_type = JobInvocation and resource_id = {job["id"]}', poll_timeout=1000 - ) - result = target_sat.api.JobInvocation(id=job['id']).read() - assert result.succeeded == 1 - - result = target_sat.cli.Host.ansible_roles_assign( - {'id': target_host.id, 'ansible-roles': f'{SELECTED_ROLE},{SELECTED_ROLE_1}'} - ) - assert 'Ansible roles were assigned to the host' in result[0]['message'] - - result = target_sat.cli.Host.ansible_roles_remove( - {'id': target_host.id, 'ansible-role': SELECTED_ROLE} - ) - assert 'Ansible role has been disassociated.' in result[0]['message'] - - result = target_sat.cli.Ansible.roles_delete({'name': SELECTED_ROLE}) - assert f'Ansible role [{SELECTED_ROLE}] was deleted.' in result[0]['message'] - - assert SELECTED_ROLE, ( - SELECTED_VAR not in target_sat.cli.Ansible.variables_info({'name': SELECTED_VAR}).stdout - ) - -@pytest.mark.e2e -@pytest.mark.tier2 -def test_add_and_remove_ansible_role_hostgroup(target_sat): + @pytest.mark.e2e + @pytest.mark.no_containers + @pytest.mark.rhel_ver_match('[^6].*') + def test_positive_ansible_e2e( + self, target_sat, module_sca_manifest_org, module_ak_with_cv, rhel_contenthost + ): + """ + Test successful execution of Ansible Job on host. + + :id: 0c52bc63-a41a-4f48-a980-fe49b4ecdbdc + + :steps: + 1. Register a content host with satellite + 2. Import a role into satellite + 3. Assign that role to a host + 4. Assert that the role and variable were assigned to the host successfully + 5. Run the Ansible playbook associated with that role + 6. Check if the job is executed successfully. + 7. Disassociate the Role from the host. + 8. Delete the assigned ansible role + + :expectedresults: + 1. Host should be assigned the proper role. + 2. Job execution must be successful. + 3. Operations performed with hammer must be successful. + + :BZ: 2154184 + + :customerscenario: true + """ + SELECTED_ROLE = 'RedHatInsights.insights-client' + SELECTED_ROLE_1 = 'theforeman.foreman_scap_client' + SELECTED_VAR = gen_string('alpha') + proxy_id = target_sat.nailgun_smart_proxy.id + # disable batch tasks to test BZ#2154184 + target_sat.cli.Settings.set({'name': 'foreman_tasks_proxy_batch_trigger', 'value': 'false'}) + result = rhel_contenthost.register( + module_sca_manifest_org, None, module_ak_with_cv.name, target_sat + ) + assert result.status == 0, f'Failed to register host: {result.stderr}' + if rhel_contenthost.os_version.major <= 7: + rhel_contenthost.create_custom_repos(rhel7=settings.repos.rhel7_os) + assert rhel_contenthost.execute('yum install -y insights-client').status == 0 + target_host = rhel_contenthost.nailgun_host + + target_sat.cli.Ansible.roles_sync( + {'role-names': f'{SELECTED_ROLE},{SELECTED_ROLE_1}', 'proxy-id': proxy_id} + ) + result = target_sat.cli.Host.ansible_roles_add( + {'id': target_host.id, 'ansible-role': SELECTED_ROLE} + ) + assert 'Ansible role has been associated.' in result[0]['message'] + + target_sat.cli.Ansible.variables_create( + {'variable': SELECTED_VAR, 'ansible-role': SELECTED_ROLE} + ) + + assert SELECTED_ROLE, ( + SELECTED_VAR in target_sat.cli.Ansible.variables_info({'name': SELECTED_VAR}).stdout + ) + template_id = ( + target_sat.api.JobTemplate() + .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] + .id + ) + job = target_sat.api.JobInvocation().run( + synchronous=False, + data={ + 'job_template_id': template_id, + 'targeting_type': 'static_query', + 'search_query': f'name = {rhel_contenthost.hostname}', + }, + ) + target_sat.wait_for_tasks( + f'resource_type = JobInvocation and resource_id = {job["id"]}', poll_timeout=1000 + ) + result = target_sat.api.JobInvocation(id=job['id']).read() + assert result.succeeded == 1 + + result = target_sat.cli.Host.ansible_roles_assign( + {'id': target_host.id, 'ansible-roles': f'{SELECTED_ROLE},{SELECTED_ROLE_1}'} + ) + assert 'Ansible roles were assigned to the host' in result[0]['message'] + + result = target_sat.cli.Host.ansible_roles_remove( + {'id': target_host.id, 'ansible-role': SELECTED_ROLE} + ) + assert 'Ansible role has been disassociated.' in result[0]['message'] + + result = target_sat.cli.Ansible.roles_delete({'name': SELECTED_ROLE}) + assert f'Ansible role [{SELECTED_ROLE}] was deleted.' in result[0]['message'] + + assert SELECTED_ROLE, ( + SELECTED_VAR not in target_sat.cli.Ansible.variables_info({'name': SELECTED_VAR}).stdout + ) + + @pytest.mark.e2e + @pytest.mark.tier2 + def test_add_and_remove_ansible_role_hostgroup(self, target_sat): + """ + Test add and remove functionality for ansible roles in hostgroup via CLI + + :id: 2c6fda14-4cd2-490a-b7ef-7a08f8164fad + + :customerscenario: true + + :steps: + 1. Create a hostgroup + 2. Sync few ansible roles + 3. Assign a few ansible roles with the host group + 4. Add some ansible role with the host group + 5. Remove the added ansible roles from the host group + + :expectedresults: + 1. Ansible role assign/add/remove functionality should work as expected in CLI + + :BZ: 2029402 + """ + ROLES = [ + 'theforeman.foreman_scap_client', + 'redhat.satellite.hostgroups', + 'RedHatInsights.insights-client', + ] + proxy_id = target_sat.nailgun_smart_proxy.id + hg_name = gen_string('alpha') + result = target_sat.cli.HostGroup.create({'name': hg_name}) + assert result['name'] == hg_name + target_sat.cli.Ansible.roles_sync({'role-names': ROLES, 'proxy-id': proxy_id}) + result = target_sat.cli.HostGroup.ansible_roles_assign( + {'name': hg_name, 'ansible-roles': f'{ROLES[1]},{ROLES[2]}'} + ) + assert 'Ansible roles were assigned to the hostgroup' in result[0]['message'] + result = target_sat.cli.HostGroup.ansible_roles_add( + {'name': hg_name, 'ansible-role': ROLES[0]} + ) + assert 'Ansible role has been associated.' in result[0]['message'] + result = target_sat.cli.HostGroup.ansible_roles_remove( + {'name': hg_name, 'ansible-role': ROLES[0]} + ) + assert 'Ansible role has been disassociated.' in result[0]['message'] + + +@pytest.mark.tier3 +@pytest.mark.upgrade +class TestAnsibleREX: + """Test class for remote execution via Ansible + + :CaseComponent: Ansible-RemoteExecution """ - Test add and remove functionality for ansible roles in hostgroup via cli - - :id: 2c6fda14-4cd2-490a-b7ef-7a08f8164fad - - :customerscenario: true - - :steps: - 1. Create a hostgroup - 2. Sync few ansible roles - 3. Assign a few ansible roles with the host group - 4. Add some ansible role with the host group - 5. Remove the added ansible roles from the host group - :expectedresults: - 1. Ansible role assign/add/remove functionality should work as expected in cli - - :BZ: 2029402 - - :CaseAutomation: Automated - """ - ROLES = [ - 'theforeman.foreman_scap_client', - 'redhat.satellite.hostgroups', - 'RedHatInsights.insights-client', - ] - proxy_id = target_sat.nailgun_smart_proxy.id - hg_name = gen_string('alpha') - result = target_sat.cli.HostGroup.create({'name': hg_name}) - assert result['name'] == hg_name - target_sat.cli.Ansible.roles_sync({'role-names': ROLES, 'proxy-id': proxy_id}) - result = target_sat.cli.HostGroup.ansible_roles_assign( - {'name': hg_name, 'ansible-roles': f'{ROLES[1]},{ROLES[2]}'} - ) - assert 'Ansible roles were assigned to the hostgroup' in result[0]['message'] - result = target_sat.cli.HostGroup.ansible_roles_add({'name': hg_name, 'ansible-role': ROLES[0]}) - assert 'Ansible role has been associated.' in result[0]['message'] - result = target_sat.cli.HostGroup.ansible_roles_remove( - {'name': hg_name, 'ansible-role': ROLES[0]} + @pytest.mark.pit_client + @pytest.mark.pit_server + @pytest.mark.rhel_ver_match('[^6]') + def test_positive_run_effective_user_job(self, rex_contenthost, target_sat): + """Tests Ansible REX job having effective user runs successfully + + :id: a5fa20d8-c2bd-4bbf-a6dc-bf307b59dd8c + + :steps: + 0. Create a VM and register to SAT and prepare for REX (ssh key) + 1. Run Ansible Command job for the host to create a user + 2. Run Ansible Command job using effective user + 3. Check the job result at the host is done under that user + + :expectedresults: multiple asserts along the code + + :parametrized: yes + """ + client = rex_contenthost + # create a user on client via remote job + username = gen_string('alpha') + filename = gen_string('alpha') + make_user_job = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Run Command - Ansible Default', + 'inputs': f'command=useradd -m {username}', + 'search-query': f'name ~ {client.hostname}', + } + ) + assert_job_invocation_result(target_sat, make_user_job['id'], client.hostname) + # create a file as new user + invocation_command = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Run Command - Ansible Default', + 'inputs': f'command=touch /home/{username}/{filename}', + 'search-query': f'name ~ {client.hostname}', + 'effective-user': username, + } + ) + assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) + + # check the file owner + result = client.execute( + f'''stat -c '%U' /home/{username}/{filename}''', + ) + # assert the file is owned by the effective user + assert username == result.stdout.strip('\n'), 'file ownership mismatch' + + @pytest.mark.rhel_ver_list([8]) + def test_positive_run_reccuring_job(self, rex_contenthost, target_sat): + """Tests Ansible REX reccuring job runs successfully multiple times + + :id: 49b0d31d-58f9-47f1-aa5d-561a1dcb0d66 + + :setup: + 1. Create a VM, register to SAT and configure REX (ssh-key) + + :steps: + 1. Run recurring Ansible Command job for the host + 2. Check the multiple job results at the host + + :expectedresults: multiple asserts along the code + + :bz: 2129432 + + :customerscenario: true + + :parametrized: yes + """ + client = rex_contenthost + invocation_command = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Run Command - Ansible Default', + 'inputs': 'command=ls', + 'search-query': f'name ~ {client.hostname}', + 'cron-line': '* * * * *', # every minute + 'max-iteration': 2, # just two runs + } + ) + result = target_sat.cli.JobInvocation.info({'id': invocation_command['id']}) + sleep(150) + rec_logic = target_sat.cli.RecurringLogic.info({'id': result['recurring-logic-id']}) + assert rec_logic['state'] == 'finished' + assert rec_logic['iteration'] == '2' + # 2129432 + rec_logic_keys = rec_logic.keys() + assert 'action' in rec_logic_keys + assert 'last-occurrence' in rec_logic_keys + assert 'next-occurrence' in rec_logic_keys + assert 'state' in rec_logic_keys + assert 'purpose' in rec_logic_keys + assert 'iteration' in rec_logic_keys + assert 'iteration-limit' in rec_logic_keys + + @pytest.mark.rhel_ver_list([8]) + def test_positive_run_concurrent_jobs(self, rex_contenthosts, target_sat): + """Tests Ansible REX concurent jobs without batch trigger + + :id: ad0f108c-03f2-49c7-8732-b1056570567b + + :steps: + 1. Create 2 hosts, disable foreman_tasks_proxy_batch_trigger + 2. Run Ansible Command job with concurrency-setting + + :expectedresults: multiple asserts along the code + + :BZ: 1817320 + + :customerscenario: true + + :parametrized: yes + """ + clients = rex_contenthosts + param_name = 'foreman_tasks_proxy_batch_trigger' + target_sat.cli.GlobalParameter().set({'name': param_name, 'value': 'false'}) + output_msgs = [] + invocation_command = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Run Command - Ansible Default', + 'inputs': 'command=ls', + 'search-query': f'name ~ {clients[0].hostname} or name ~ {clients[1].hostname}', + 'concurrency-level': 2, + } + ) + for vm in clients: + output_msgs.append( + 'host output from {}: {}'.format( + vm.hostname, + ' '.join( + target_sat.cli.JobInvocation.get_output( + {'id': invocation_command['id'], 'host': vm.hostname} + ) + ), + ) + ) + result = target_sat.cli.JobInvocation.info({'id': invocation_command['id']}) + assert result['success'] == '2', output_msgs + target_sat.cli.GlobalParameter().delete({'name': param_name}) + assert len(target_sat.cli.GlobalParameter().list({'search': param_name})) == 0 + + @pytest.mark.rhel_ver_list([8]) + def test_positive_run_serial(self, rex_contenthosts, target_sat): + """Tests subtasks in a job run one by one when concurrency level set to 1 + + :id: 5ce39447-82d0-42df-81be-16ed3d67a2a4 + + :setup: + 1. Create 2 hosts, register to SAT and configure REX (ssh-key) + + :steps: + 1. Run a bash command job with concurrency level 1 + + :expectedresults: First subtask should run immediately, second one after the first one finishes + + :parametrized: yes + """ + hosts = rex_contenthosts + output_msgs = [] + template_file = f'/root/{gen_string("alpha")}.template' + target_sat.execute( + f"echo 'rm /root/test-<%= @host %>; echo $(date +%s) >> /root/test-<%= @host %>; sleep 120; echo $(date +%s) >> /root/test-<%= @host %>' > {template_file}" + ) + template = target_sat.cli.JobTemplate.create( + { + 'name': gen_string('alpha'), + 'file': template_file, + 'job-category': 'Commands', + 'provider-type': 'script', + } + ) + invocation = target_sat.cli_factory.job_invocation( + { + 'job-template': template['name'], + 'search-query': f'name ~ {hosts[0].hostname} or name ~ {hosts[1].hostname}', + 'concurrency-level': 1, + } + ) + for vm in hosts: + output_msgs.append( + 'host output from {}: {}'.format( + vm.hostname, + ' '.join( + target_sat.cli.JobInvocation.get_output( + {'id': invocation['id'], 'host': vm.hostname} + ) + ), + ) + ) + result = target_sat.cli.JobInvocation.info({'id': invocation['id']}) + assert result['success'] == '2', output_msgs + # assert for time diffs + file1 = hosts[0].execute('cat /root/test-$(hostname)').stdout + file2 = hosts[1].execute('cat /root/test-$(hostname)').stdout + file1_start, file1_end = map(int, file1.rstrip().split('\n')) + file2_start, file2_end = map(int, file2.rstrip().split('\n')) + if file1_start > file2_start: + file1_start, file1_end, file2_start, file2_end = ( + file2_start, + file2_end, + file1_start, + file1_end, + ) + assert file1_end - file1_start >= 120 + assert file2_end - file2_start >= 120 + assert file2_start >= file1_end # the jobs did NOT run concurrently + + @pytest.mark.e2e + @pytest.mark.no_containers + @pytest.mark.pit_server + @pytest.mark.rhel_ver_match('[^6].*') + @pytest.mark.skipif( + (not settings.robottelo.repos_hosting_url), reason='Missing repos_hosting_url' ) - assert 'Ansible role has been disassociated.' in result[0]['message'] + def test_positive_run_packages_and_services_job( + self, rhel_contenthost, module_sca_manifest_org, module_ak_with_cv, target_sat + ): + """Tests Ansible REX job can install packages and start services + + :id: 47ed82fb-77ca-43d6-a52e-f62bae5d3a42 + + :setup: + 1. Create a VM, register to SAT and configure REX (ssh-key) + + :steps: + 1. Run Ansible Package job for the host to install a package + 2. Check the package is present at the host + 3. Run Ansible Service job for the host to start a service + 4. Check the service is started on the host + + :expectedresults: multiple asserts along the code + + :bz: 1872688, 1811166 + + :customerscenario: true + + :parametrized: yes + """ + client = rhel_contenthost + packages = ['tapir'] + result = client.register( + module_sca_manifest_org, + None, + module_ak_with_cv.name, + target_sat, + repo=settings.repos.yum_3.url, + ) + assert result.status == 0, f'Failed to register host: {result.stderr}' + # install package + invocation_command = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Package Action - Ansible Default', + 'inputs': 'state=latest, name={}'.format(*packages), + 'search-query': f'name ~ {client.hostname}', + } + ) + assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) + result = client.run(f'rpm -q {" ".join(packages)}') + assert result.status == 0 + + # stop a service + service = 'rsyslog' + invocation_command = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Service Action - Ansible Default', + 'inputs': f'state=stopped, name={service}', + 'search-query': f"name ~ {client.hostname}", + } + ) + assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) + result = client.execute(f'systemctl status {service}') + assert result.status == 3 + + # start it again + invocation_command = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Service Action - Ansible Default', + 'inputs': f'state=started, name={service}', + 'search-query': f'name ~ {client.hostname}', + } + ) + assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) + result = client.execute(f'systemctl status {service}') + assert result.status == 0 + + @pytest.mark.rhel_ver_list([8]) + def test_positive_install_ansible_collection(self, rex_contenthost, target_sat): + """Test whether Ansible collection can be installed via Ansible REX + + :id: ad25aee5-4ea3-4743-a301-1c6271856f79 + + :steps: + 1. Upload a manifest. + 2. Register content host to Satellite with REX setup + 3. Enable Ansible repo on content host. + 4. Install ansible or ansible-core package + 5. Run REX job to install Ansible collection on content host. + + :expectedresults: Ansible collection can be installed on content host via REX. + """ + client = rex_contenthost + # Enable Ansible repository and Install ansible or ansible-core package + client.create_custom_repos(rhel8_aps=settings.repos.rhel8_os.appstream) + assert client.execute('dnf -y install ansible-core').status == 0 + + collection_job = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Ansible Collection - Install from Galaxy', + 'inputs': 'ansible_collections_list="oasis_roles.system"', + 'search-query': f'name ~ {client.hostname}', + } + ) + result = target_sat.cli.JobInvocation.info({'id': collection_job['id']}) + assert result['success'] == '1' + collection_path = client.execute('ls /etc/ansible/collections/ansible_collections').stdout + assert 'oasis_roles' in collection_path + + # Extend test with custom collections_path advanced input field + collection_job = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Ansible Collection - Install from Galaxy', + 'inputs': 'ansible_collections_list="oasis_roles.system", collections_path="~/"', + 'search-query': f'name ~ {client.hostname}', + } + ) + result = target_sat.cli.JobInvocation.info({'id': collection_job['id']}) + assert result['success'] == '1' + collection_path = client.execute('ls ~/ansible_collections').stdout + assert 'oasis_roles' in collection_path diff --git a/tests/foreman/cli/test_bootdisk.py b/tests/foreman/cli/test_bootdisk.py index 4775c3bbdea..5e0ca29eec6 100644 --- a/tests/foreman/cli/test_bootdisk.py +++ b/tests/foreman/cli/test_bootdisk.py @@ -20,6 +20,7 @@ @pytest.mark.rhel_ver_match('[^6]') def test_positive_bootdisk_download_https( + request, module_location, module_sync_kickstart_content, module_provisioning_capsule, @@ -79,8 +80,12 @@ def test_positive_bootdisk_download_https( 'lifecycle-environment-id': module_lce_library.id, } ) + + @request.addfinalizer + def _finalize(): + module_target_sat.api.Host(id=host.id).delete() + module_target_sat.api.Media(id=media['id']).delete() + # Check if full-host bootdisk can be downloaded. bootdisk = module_target_sat.cli.Bootdisk.host({'host-id': host['id'], 'full': 'true'}) assert 'Successfully downloaded host disk image' in bootdisk['message'] - module_target_sat.api.Host(id=host.id).delete() - module_target_sat.api.Media(id=media['id']).delete() diff --git a/tests/foreman/cli/test_capsule.py b/tests/foreman/cli/test_capsule.py index e41759fbc2b..ddcc100326a 100644 --- a/tests/foreman/cli/test_capsule.py +++ b/tests/foreman/cli/test_capsule.py @@ -1,10 +1,10 @@ """Test class for the capsule CLI. -:Requirement: Capsule +:Requirement: ForemanProxy :CaseAutomation: Automated -:CaseComponent: Capsule +:CaseComponent: ForemanProxy :Team: Platform diff --git a/tests/foreman/cli/test_classparameters.py b/tests/foreman/cli/test_classparameters.py index a4e75c1f76d..022ceed0aac 100644 --- a/tests/foreman/cli/test_classparameters.py +++ b/tests/foreman/cli/test_classparameters.py @@ -86,8 +86,8 @@ def test_positive_list( location=module_puppet_loc.id, environment=module_puppet['env'].name, ).create() - host.add_puppetclass(data={'puppetclass_id': module_puppet['class']['id']}) request.addfinalizer(host.delete) + host.add_puppetclass(data={'puppetclass_id': module_puppet['class']['id']}) hostgroup = session_puppet_enabled_sat.cli_factory.hostgroup( { 'puppet-environment-id': module_puppet['env'].id, diff --git a/tests/foreman/cli/test_computeresource_azurerm.py b/tests/foreman/cli/test_computeresource_azurerm.py index 1621b48d12f..1756a0ed674 100644 --- a/tests/foreman/cli/test_computeresource_azurerm.py +++ b/tests/foreman/cli/test_computeresource_azurerm.py @@ -37,7 +37,7 @@ def azurerm_hostgroup( ): """Sets Hostgroup for AzureRm Host Provisioning""" - hgroup = sat_azure.api.HostGroup( + return sat_azure.api.HostGroup( architecture=sat_azure_default_architecture, compute_resource=module_azurerm_cr, domain=sat_azure_domain, @@ -46,7 +46,6 @@ def azurerm_hostgroup( operatingsystem=sat_azure_default_os, organization=[sat_azure_org], ).create() - return hgroup class TestAzureRMComputeResourceTestCase: @@ -344,29 +343,30 @@ def class_host_ft( Provisions the host on AzureRM using Finish template Later in tests this host will be used to perform assertions """ - with sat_azure.hammer_api_timeout(): - with sat_azure.skip_yum_update_during_provisioning(template='Kickstart default finish'): - host = sat_azure.cli.Host.create( - { - 'name': self.hostname, - 'compute-resource': module_azurerm_cr.name, - 'compute-attributes': self.compute_attrs, - 'interface': self.interfaces_attributes, - 'location-id': sat_azure_loc.id, - 'organization-id': sat_azure_org.id, - 'domain-id': sat_azure_domain.id, - 'domain': sat_azure_domain.name, - 'architecture-id': sat_azure_default_architecture.id, - 'operatingsystem-id': sat_azure_default_os.id, - 'root-password': gen_string('alpha'), - 'image': module_azurerm_custom_finishimg.name, - }, - timeout=1800000, - ) - yield host - with sat_azure.api_factory.satellite_setting('destroy_vm_on_host_delete=True'): - if sat_azure.cli.Host.exists(search=('name', host['name'])): - sat_azure.cli.Host.delete({'name': self.fullhostname}, timeout=1800000) + with sat_azure.hammer_api_timeout(), sat_azure.skip_yum_update_during_provisioning( + template='Kickstart default finish' + ): + host = sat_azure.cli.Host.create( + { + 'name': self.hostname, + 'compute-resource': module_azurerm_cr.name, + 'compute-attributes': self.compute_attrs, + 'interface': self.interfaces_attributes, + 'location-id': sat_azure_loc.id, + 'organization-id': sat_azure_org.id, + 'domain-id': sat_azure_domain.id, + 'domain': sat_azure_domain.name, + 'architecture-id': sat_azure_default_architecture.id, + 'operatingsystem-id': sat_azure_default_os.id, + 'root-password': gen_string('alpha'), + 'image': module_azurerm_custom_finishimg.name, + }, + timeout=1800000, + ) + yield host + with sat_azure.api_factory.satellite_setting('destroy_vm_on_host_delete=True'): + if sat_azure.cli.Host.exists(search=('name', host['name'])): + sat_azure.cli.Host.delete({'name': self.fullhostname}, timeout=1800000) @pytest.fixture(scope='class') def azureclient_host(self, azurermclient, class_host_ft): @@ -472,27 +472,26 @@ def class_host_ud( Provisions the host on AzureRM using UserData template Later in tests this host will be used to perform assertions """ - with sat_azure.hammer_api_timeout(): - with sat_azure.skip_yum_update_during_provisioning( - template='Kickstart default user data' - ): - host = sat_azure.cli.Host.create( - { - 'name': self.hostname, - 'compute-attributes': self.compute_attrs, - 'interface': self.interfaces_attributes, - 'image': module_azurerm_cloudimg.name, - 'hostgroup': azurerm_hostgroup.name, - 'location': sat_azure_loc.name, - 'organization': sat_azure_org.name, - 'operatingsystem-id': sat_azure_default_os.id, - }, - timeout=1800000, - ) - yield host - with sat_azure.api_factory.satellite_setting('destroy_vm_on_host_delete=True'): - if sat_azure.cli.Host.exists(search=('name', host['name'])): - sat_azure.cli.Host.delete({'name': self.fullhostname}, timeout=1800000) + with sat_azure.hammer_api_timeout(), sat_azure.skip_yum_update_during_provisioning( + template='Kickstart default user data' + ): + host = sat_azure.cli.Host.create( + { + 'name': self.hostname, + 'compute-attributes': self.compute_attrs, + 'interface': self.interfaces_attributes, + 'image': module_azurerm_cloudimg.name, + 'hostgroup': azurerm_hostgroup.name, + 'location': sat_azure_loc.name, + 'organization': sat_azure_org.name, + 'operatingsystem-id': sat_azure_default_os.id, + }, + timeout=1800000, + ) + yield host + with sat_azure.api_factory.satellite_setting('destroy_vm_on_host_delete=True'): + if sat_azure.cli.Host.exists(search=('name', host['name'])): + sat_azure.cli.Host.delete({'name': self.fullhostname}, timeout=1800000) @pytest.fixture(scope='class') def azureclient_host(self, azurermclient, class_host_ud): diff --git a/tests/foreman/cli/test_computeresource_libvirt.py b/tests/foreman/cli/test_computeresource_libvirt.py index a53959f277b..2a4720750a0 100644 --- a/tests/foreman/cli/test_computeresource_libvirt.py +++ b/tests/foreman/cli/test_computeresource_libvirt.py @@ -364,7 +364,7 @@ def test_negative_update(libvirt_url, options, module_target_sat): # check attributes have not changed assert result['name'] == comp_res['name'] options.pop('new-name', None) - for key in options.keys(): + for key in options: assert comp_res[key] == result[key] diff --git a/tests/foreman/cli/test_computeresource_osp.py b/tests/foreman/cli/test_computeresource_osp.py index d88daaf1ad8..ff6779056ae 100644 --- a/tests/foreman/cli/test_computeresource_osp.py +++ b/tests/foreman/cli/test_computeresource_osp.py @@ -76,6 +76,7 @@ def test_crud_and_duplicate_name(self, request, id_type, osp_version, target_sat 'url': osp_version, } ) + request.addfinalizer(lambda: self.cr_cleanup(compute_resource['id'], id_type, target_sat)) assert compute_resource['name'] == name assert target_sat.cli.ComputeResource.exists(search=(id_type, compute_resource[id_type])) @@ -102,7 +103,6 @@ def test_crud_and_duplicate_name(self, request, id_type, osp_version, target_sat else: compute_resource = target_sat.cli.ComputeResource.info({'id': compute_resource['id']}) assert new_name == compute_resource['name'] - request.addfinalizer(lambda: self.cr_cleanup(compute_resource['id'], id_type, target_sat)) @pytest.mark.tier3 def test_negative_create_osp_with_url(self, target_sat): diff --git a/tests/foreman/cli/test_computeresource_vmware.py b/tests/foreman/cli/test_computeresource_vmware.py index b72a5fca5c5..62ee4a71505 100644 --- a/tests/foreman/cli/test_computeresource_vmware.py +++ b/tests/foreman/cli/test_computeresource_vmware.py @@ -13,7 +13,6 @@ from fauxfactory import gen_string import pytest from wait_for import wait_for -from wrapanapi import VMWareSystem from robottelo.config import settings from robottelo.constants import FOREMAN_PROVIDERS @@ -93,6 +92,7 @@ def test_positive_provision_end_to_end( module_vmware_hostgroup, provision_method, vmware, + vmwareclient, ): """Provision a host on vmware compute resource with the help of hostgroup. @@ -139,12 +139,7 @@ def test_positive_provision_end_to_end( hostname = f'{hostname}.{module_provisioning_sat.domain.name}' assert hostname == host['name'] # check if vm is created on vmware - vmware = VMWareSystem( - hostname=vmware.hostname, - username=settings.vmware.username, - password=settings.vmware.password, - ) - assert vmware.does_vm_exist(hostname) is True + assert vmwareclient.does_vm_exist(hostname) is True wait_for( lambda: sat.cli.Host.info({'name': hostname})['status']['build-status'] != 'Pending installation', diff --git a/tests/foreman/cli/test_contentview.py b/tests/foreman/cli/test_contentview.py index 0b4ee4041ef..c2de286b9f6 100644 --- a/tests/foreman/cli/test_contentview.py +++ b/tests/foreman/cli/test_contentview.py @@ -233,7 +233,7 @@ def test_positive_update_filter(self, repo_setup, module_target_sat): } ) cvf = module_target_sat.cli.ContentView.filter.info({'id': cvf['filter-id']}) - assert 'security' == cvf['rules'][0]['types'] + assert cvf['rules'][0]['types'] == 'security' @pytest.mark.tier1 def test_positive_delete_by_id(self, module_org, module_target_sat): @@ -3798,7 +3798,7 @@ def test_negative_user_with_no_create_view_cv_permissions(self, module_org, modu password = gen_alphanumeric() no_rights_user = module_target_sat.cli_factory.user({'password': password}) no_rights_user['password'] = password - org_id = module_target_sat.cli_factory.make_org(cached=True)['id'] + org_id = module_target_sat.cli_factory.make_org()['id'] for name in generate_strings_list(exclude_types=['cjk']): # test that user can't create with pytest.raises(CLIReturnCodeError): @@ -4065,6 +4065,75 @@ def test_version_info_by_lce(self, module_org, module_target_sat): ) assert content_view['version'] == '1.0' + def test_positive_validate_force_promote_warning(self, target_sat, function_org): + """Test cv promote shows warning of 'force promotion' for out of sequence LCE + + :id: 1bfb76be-ab40-48b4-b5a3-428a2a9ab99b + + :steps: + 1. Create an environment path ex- Library >> Test >> Preprod >> Prod + 2. Create a CV and publish into the Library + 3. Promote version 1.0 to Preprod, skip Test, this should fail with warning + 4. Promote version 1.0 to Preprod using force, this should success + 5. Try to promote version 1.0 from Preprod to Prod, this should success without warning + + :expectedresults: + 1. CV version 1.0 should be present on Prod LCE + + :CaseImportance: High + + :BZ: 2125728 + + :customerscenario: true + """ + # Create an environment path ex- Library >> Test >> Preprod >> Prod + lce_test = target_sat.cli_factory.make_lifecycle_environment( + {'organization-id': function_org.id} + ) + lce_preprod = target_sat.cli_factory.make_lifecycle_environment( + {'organization-id': function_org.id, 'prior': lce_test['name']} + ) + lce_prod = target_sat.cli_factory.make_lifecycle_environment( + {'organization-id': function_org.id, 'prior': lce_preprod['name']} + ) + + # Create a CV and publish into the Library + cv = target_sat.cli_factory.make_content_view({'organization-id': function_org.id}) + target_sat.cli.ContentView.publish({'id': cv['id']}) + + # Promote version 1.0 to Preprod, skip Test, this should fail with warning + cv_version = target_sat.cli.ContentView.info({'id': cv['id']})['versions'][0] + with pytest.raises(CLIReturnCodeError) as error: + target_sat.cli.ContentView.version_promote( + {'id': cv_version['id'], 'to-lifecycle-environment-id': lce_preprod['id']} + ) + assert ( + 'Cannot promote environment out of sequence. Use force to bypass restriction' + in error.value.stderr + ) + + # Promote version 1.0 to Preprod using force, this should success + target_sat.cli.ContentView.version_promote( + { + 'id': cv_version['id'], + 'to-lifecycle-environment-id': lce_preprod['id'], + 'force': True, + } + ) + promoted_lce = target_sat.cli.ContentView.info({'id': cv['id']})['lifecycle-environments'][ + -1 + ] + assert lce_preprod['id'] == promoted_lce['id'] + + # Try to promote version 1.0 from Preprod to Prod, this should success without warning + target_sat.cli.ContentView.version_promote( + {'id': cv_version['id'], 'to-lifecycle-environment-id': lce_prod['id']} + ) + promoted_lce = target_sat.cli.ContentView.info({'id': cv['id']})['lifecycle-environments'][ + -1 + ] + assert lce_prod['id'] == promoted_lce['id'] + class TestContentViewFileRepo: """Specific tests for Content Views with File Repositories containing diff --git a/tests/foreman/cli/test_discoveredhost.py b/tests/foreman/cli/test_discoveredhost.py index 2cd1297224e..e578f35e1ac 100644 --- a/tests/foreman/cli/test_discoveredhost.py +++ b/tests/foreman/cli/test_discoveredhost.py @@ -70,10 +70,8 @@ def test_rhel_pxe_discovery_provisioning( assert 'Host created' in result[0]['message'] host = sat.api.Host().search(query={"search": f'id={discovered_host.id}'})[0] - assert host - - # teardown request.addfinalizer(lambda: sat.provisioning_cleanup(host.name)) + assert host wait_for( lambda: host.read().build_status_label != 'Pending installation', @@ -131,10 +129,8 @@ def test_rhel_pxeless_discovery_provisioning( ) assert 'Host created' in result[0]['message'] host = sat.api.Host().search(query={"search": f'id={discovered_host.id}'})[0] - assert host - - # teardown request.addfinalizer(lambda: sat.provisioning_cleanup(host.name)) + assert host wait_for( lambda: host.read().build_status_label != 'Pending installation', diff --git a/tests/foreman/cli/test_docker.py b/tests/foreman/cli/test_docker.py index a4dd51c17c2..10b1d1dd4fc 100644 --- a/tests/foreman/cli/test_docker.py +++ b/tests/foreman/cli/test_docker.py @@ -114,7 +114,7 @@ def test_positive_read_docker_tags(self, repo, module_target_sat): manifests_list = module_target_sat.cli.Docker.manifest.list({'repository-id': repo['id']}) # Some manifests do not have tags associated with it, ignore those # because we want to check the tag information - manifests = [m_iter for m_iter in manifests_list if not m_iter['tags'] == ''] + manifests = [m_iter for m_iter in manifests_list if m_iter['tags'] != ''] assert manifests tags_list = module_target_sat.cli.Docker.tag.list({'repository-id': repo['id']}) # Extract tag names for the repository out of docker tag list diff --git a/tests/foreman/cli/test_domain.py b/tests/foreman/cli/test_domain.py index 8b1c157c657..32f81369592 100644 --- a/tests/foreman/cli/test_domain.py +++ b/tests/foreman/cli/test_domain.py @@ -42,8 +42,7 @@ def valid_create_params(): @filtered_datapoint def invalid_create_params(): """Returns a list of invalid domain create parameters""" - params = [{'name': gen_string(str_type='utf8', length=256)}] - return params + return [{'name': gen_string(str_type='utf8', length=256)}] @filtered_datapoint @@ -66,8 +65,7 @@ def valid_update_params(): @filtered_datapoint def invalid_update_params(): """Returns a list of invalid domain update parameters""" - params = [{'name': ''}, {'name': gen_string(str_type='utf8', length=256)}] - return params + return [{'name': ''}, {'name': gen_string(str_type='utf8', length=256)}] @filtered_datapoint @@ -218,7 +216,7 @@ def test_negative_update(module_domain, options, module_target_sat): module_target_sat.cli.Domain.update(dict(options, id=module_domain.id)) # check - domain not updated result = module_target_sat.cli.Domain.info({'id': module_domain.id}) - for key in options.keys(): + for key in options: assert result[key] == getattr(module_domain, key) diff --git a/tests/foreman/cli/test_errata.py b/tests/foreman/cli/test_errata.py index bbfe7af1380..0dc3e091654 100644 --- a/tests/foreman/cli/test_errata.py +++ b/tests/foreman/cli/test_errata.py @@ -12,6 +12,7 @@ """ from datetime import date, datetime, timedelta from operator import itemgetter +import re from broker import Broker import pytest @@ -358,7 +359,7 @@ def filter_sort_errata(sat, org, sort_by_date='issued', filter_by_org=None): elif filter_by_org == 'label': list_param['organization-label'] = org.label - sort_reversed = True if sort_order == 'DESC' else False + sort_reversed = sort_order == 'DESC' errata_list = sat.cli.Erratum.list(list_param) assert len(errata_list) > 0 @@ -688,16 +689,11 @@ def test_positive_list_affected_chosts_by_erratum_restrict_flag( 'inclusion': 'false', } ) - - @request.addfinalizer - def cleanup(): - cv_filter_cleanup( - target_sat, - cv_filter['filter-id'], - module_cv, - module_sca_manifest_org, - module_lce, + request.addfinalizer( + lambda: cv_filter_cleanup( + target_sat, cv_filter['filter-id'], module_cv, module_sca_manifest_org, module_lce ) + ) # Make rule to hide the RPM that creates the need for the installable erratum target_sat.cli_factory.content_view_filter_rule( @@ -861,17 +857,11 @@ def test_host_errata_search_commands( 'inclusion': 'false', } ) - - @request.addfinalizer - def cleanup(): - cv_filter_cleanup( - target_sat, - cv_filter['filter-id'], - module_cv, - module_sca_manifest_org, - module_lce, + request.addfinalizer( + lambda: cv_filter_cleanup( + target_sat, cv_filter['filter-id'], module_cv, module_sca_manifest_org, module_lce ) - + ) # Make rule to exclude the specified bugfix package target_sat.cli_factory.content_view_filter_rule( { @@ -1275,7 +1265,7 @@ def test_apply_errata_using_default_content_view(errata_host, module_sca_manifes # job invocation started, check status assert 'created' in _job_invoc[0]['message'] assert (_id := _job_invoc[0]['id']) - assert 'succeeded' == target_sat.cli.JobInvocation().info(options={'id': _id})['status'] + assert target_sat.cli.JobInvocation().info(options={'id': _id})['status'] == 'succeeded' start_and_wait_errata_recalculate(target_sat, errata_host) # Assert that the erratum is no longer applicable @@ -1331,7 +1321,7 @@ def test_update_applicable_package_using_default_content_view(errata_host, targe # job invocation created, assert status assert 'created' in _job_invoc[0]['message'] assert (_id := _job_invoc[0]['id']) - assert 'succeeded' == target_sat.cli.JobInvocation().info(options={'id': _id})['status'] + assert target_sat.cli.JobInvocation().info(options={'id': _id})['status'] == 'succeeded' start_and_wait_errata_recalculate(target_sat, errata_host) # Assert that the package is no longer applicable applicable_packages = target_sat.cli.Package.list( @@ -1545,3 +1535,44 @@ def test_errata_list_by_contentview_filter(module_sca_manifest_org, module_targe ) ) assert errata_count != errata_count_cvf + + +@pytest.mark.rhel_ver_match('8') +def test_positive_verify_errata_recalculate_tasks(target_sat, errata_host): + """Verify 'Actions::Katello::Applicability::Hosts::BulkGenerate' tasks proceed on 'worker-hosts-queue-1.service' + + :id: d5f89df2-b8fb-4aec-839d-b548b0aadc0c + + :setup: Register host which has applicable errata with satellite + + :steps: + 1. Run 'hammer host errata recalculate --host-id NUMBER' (will trigger inside errata_host fixture) + 2. Check systemctl or journalctl or /var/log/messages for 'dynflow-sidekiq@worker-hosts-queue-1.service' + + :expectedresults: worker-hosts-queue-1 should proceed with task and this can be cross verify using unique PID + + :customerscenario: true + + :BZ: 2249736 + """ + # Recalculate errata command has triggered inside errata_host fixture + + # get PID of 'worker-hosts-queue-1' from /var/log/messages + message_log = target_sat.execute( + 'grep "dynflow-sidekiq@worker-hosts-queue-1" /var/log/messages | tail -1' + ) + assert message_log.status == 0 + pattern_1 = r'\[(.*?)\]' # pattern to capture PID of 'worker-hosts-queue-1' + match = re.search(pattern_1, message_log.stdout) + pid_log_message = match.group(1) + + # get PID of 'worker-hosts-queue-1' from systemctl command output + systemctl_log = target_sat.execute( + 'systemctl status dynflow-sidekiq@worker-hosts-queue-1.service | grep -i "Main PID:"' + ) + assert systemctl_log.status == 0 + pattern_2 = r'\: (.*?) \(' # pattern to capture PID of 'worker-hosts-queue-1' + match = re.search(pattern_2, systemctl_log.stdout) + pid_systemctl_cmd = match.group(1) + + assert pid_log_message == pid_systemctl_cmd diff --git a/tests/foreman/cli/test_filter.py b/tests/foreman/cli/test_filter.py index 9c12b239089..4108ce07d6b 100644 --- a/tests/foreman/cli/test_filter.py +++ b/tests/foreman/cli/test_filter.py @@ -19,13 +19,12 @@ @pytest.fixture(scope='module') def module_perms(module_target_sat): """Search for provisioning template permissions. Set ``cls.ct_perms``.""" - perms = [ + return [ permission['name'] for permission in module_target_sat.cli.Filter.available_permissions( {"search": "resource_type=User"} ) ] - return perms @pytest.fixture diff --git a/tests/foreman/cli/test_hammer.py b/tests/foreman/cli/test_hammer.py index c795352c5ed..aa3bfcf4aeb 100644 --- a/tests/foreman/cli/test_hammer.py +++ b/tests/foreman/cli/test_hammer.py @@ -136,6 +136,13 @@ def test_positive_disable_hammer_defaults(request, function_product, target_sat) :BZ: 1640644, 1368173 """ + + @request.addfinalizer + def _finalize(): + target_sat.cli.Defaults.delete({'param-name': 'organization_id'}) + result = target_sat.execute('hammer defaults list') + assert str(function_product.organization.id) not in result.stdout + target_sat.cli.Defaults.add( {'param-name': 'organization_id', 'param-value': function_product.organization.id} ) @@ -154,12 +161,6 @@ def test_positive_disable_hammer_defaults(request, function_product, target_sat) assert result.status == 0 assert function_product.name in result.stdout - @request.addfinalizer - def _finalize(): - target_sat.cli.Defaults.delete({'param-name': 'organization_id'}) - result = target_sat.execute('hammer defaults list') - assert str(function_product.organization.id) not in result.stdout - @pytest.mark.upgrade def test_positive_check_debug_log_levels(target_sat): diff --git a/tests/foreman/cli/test_host.py b/tests/foreman/cli/test_host.py index f7e69591f23..fe01b0bc973 100644 --- a/tests/foreman/cli/test_host.py +++ b/tests/foreman/cli/test_host.py @@ -232,7 +232,7 @@ def parse_cli_entity_list_help_message(help_message): name = name[:-1] # remove colon from name if 'Usage' in name: continue - elif 'Options' in name: + if 'Options' in name: # used together with previous_line when line (message) is appended to previous line options = parse_two_columns(content, options_start_with_dash=True) elif 'field sets' in name: @@ -1061,18 +1061,18 @@ def test_positive_parameter_crud(function_host, target_sat): {'host-id': function_host['id'], 'name': name, 'value': value} ) host = target_sat.cli.Host.info({'id': function_host['id']}) - assert name in host['parameters'].keys() + assert name in host['parameters'] assert value == host['parameters'][name] new_value = valid_data_list()[name] target_sat.cli.Host.set_parameter({'host-id': host['id'], 'name': name, 'value': new_value}) host = target_sat.cli.Host.info({'id': host['id']}) - assert name in host['parameters'].keys() + assert name in host['parameters'] assert new_value == host['parameters'][name] target_sat.cli.Host.delete_parameter({'host-id': host['id'], 'name': name}) host = target_sat.cli.Host.info({'id': host['id']}) - assert name not in host['parameters'].keys() + assert name not in host['parameters'] # -------------------------- HOST PARAMETER SCENARIOS ------------------------- @@ -1098,7 +1098,7 @@ def test_negative_add_parameter(function_host, target_sat): } ) host = target_sat.cli.Host.info({'id': function_host['id']}) - assert name not in host['parameters'].keys() + assert name not in host['parameters'] @pytest.mark.cli_host_parameter @@ -2014,16 +2014,19 @@ def test_negative_without_attach( @pytest.mark.cli_host_subscription @pytest.mark.tier3 def test_negative_without_attach_with_lce( - target_sat, host_subscription_client, function_org, function_lce + target_sat, + host_subscription_client, + function_org, + function_lce, ): """Attempt to enable a repository of a subscription that was not - attached to a host + attached to a host. This test is not using the host_subscription entities except subscription_name and repository_id :id: fc469e70-a7cb-4fca-b0ea-3c9e3dfff849 - :expectedresults: repository not enabled on host + :expectedresults: Repository enabled due to SCA. Why is this "negative"? To keep history, because pre-6.16, this would have failed. :parametrized: yes """ @@ -2035,8 +2038,8 @@ def test_negative_without_attach_with_lce( target_sat.cli_factory.setup_org_for_a_rh_repo( { 'product': PRDS['rhel'], - 'repository-set': REPOSET['rhst7'], - 'repository': REPOS['rhst7']['name'], + 'repository-set': REPOSET['rhsclient7'], + 'repository': REPOS['rhsclient7']['name'], 'organization-id': function_org.id, 'content-view-id': content_view.id, 'lifecycle-environment-id': function_lce.id, @@ -2045,27 +2048,18 @@ def test_negative_without_attach_with_lce( }, force_use_cdn=True, ) - host_lce = target_sat.api.LifecycleEnvironment(organization=function_org).create() - # refresh content view data - content_view.publish() - content_view.read().version[-1].promote(data={'environment_ids': host_lce.id, 'force': False}) # register client host_subscription_client.register_contenthost( function_org.name, - lce=f'{host_lce.name}/{content_view.name}', + lce=f'{function_lce.name}/{content_view.name}', auto_attach=False, ) - # get list of available subscriptions which are matched with default subscription - subscriptions = host_subscription_client.run( - f'subscription-manager list --available --matches "{DEFAULT_SUBSCRIPTION_NAME}" --pool-only' - ) - pool_id = subscriptions.stdout.strip() - # attach to plain RHEL subsctiption - host_subscription_client.subscription_manager_attach_pool([pool_id]) assert host_subscription_client.subscribed - host_subscription_client.enable_repo(REPOS['rhst7']['id']) + res = host_subscription_client.enable_repo(REPOS['rhsclient7']['id']) + assert res.status == 0 + assert f"Repository '{REPOS['rhsclient7']['id']}' is enabled for this system." in res.stdout @pytest.mark.e2e diff --git a/tests/foreman/cli/test_installer.py b/tests/foreman/cli/test_installer.py index 4a42eaa1315..cf0eb838ee0 100644 --- a/tests/foreman/cli/test_installer.py +++ b/tests/foreman/cli/test_installer.py @@ -1,10 +1,10 @@ """Tests For Disconnected Satellite Installation -:Requirement: Installer (disconnected satellite installation) +:Requirement: Installation (disconnected satellite installation) :CaseAutomation: Automated -:CaseComponent: Installer +:CaseComponent: Installation :Team: Platform diff --git a/tests/foreman/cli/test_ldapauthsource.py b/tests/foreman/cli/test_ldapauthsource.py index 528a47420a5..e3cb8bb32ab 100644 --- a/tests/foreman/cli/test_ldapauthsource.py +++ b/tests/foreman/cli/test_ldapauthsource.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: LDAP +:CaseComponent: Authentication :Team: Endeavour @@ -134,7 +134,7 @@ def test_positive_refresh_usergroup_with_ad(self, member_group, ad_data, module_ result = module_target_sat.cli.Auth.with_user( username=ad_data['ldap_user_name'], password=ad_data['ldap_user_passwd'] ).status() - assert LOGEDIN_MSG.format(ad_data['ldap_user_name']) in result[0]['message'] + assert LOGEDIN_MSG.format(ad_data['ldap_user_name']) in result.split("\n")[1] module_target_sat.cli.UserGroupExternal.refresh( {'user-group-id': user_group['id'], 'name': member_group} ) diff --git a/tests/foreman/cli/test_leapp_client.py b/tests/foreman/cli/test_leapp_client.py index 6ab248b0f92..4ea36bbf37a 100644 --- a/tests/foreman/cli/test_leapp_client.py +++ b/tests/foreman/cli/test_leapp_client.py @@ -91,8 +91,7 @@ def function_leapp_cv(module_target_sat, module_sca_manifest_org, leapp_repos, m function_leapp_cv.publish() cvv = function_leapp_cv.read().version[0] cvv.promote(data={'environment_ids': module_leapp_lce.id, 'force': True}) - function_leapp_cv = function_leapp_cv.read() - return function_leapp_cv + return function_leapp_cv.read() @pytest.fixture @@ -121,7 +120,7 @@ def leapp_repos( source = upgrade_path['source_version'] target = upgrade_path['target_version'] all_repos = [] - for rh_repo_key in RHEL_REPOS.keys(): + for rh_repo_key in RHEL_REPOS: release_version = RHEL_REPOS[rh_repo_key]['releasever'] if release_version in str(source) or release_version in target: prod = 'rhel' if 'rhel7' in rh_repo_key else rh_repo_key.split('_')[0] diff --git a/tests/foreman/cli/test_logging.py b/tests/foreman/cli/test_logging.py index 8d315b3e59c..bb7daceb606 100644 --- a/tests/foreman/cli/test_logging.py +++ b/tests/foreman/cli/test_logging.py @@ -14,7 +14,6 @@ import re from fauxfactory import gen_string -from nailgun import entities import pytest from robottelo.config import settings @@ -130,7 +129,7 @@ def test_positive_logging_from_foreman_proxy(target_sat): @pytest.mark.tier4 -def test_positive_logging_from_candlepin(module_org, module_entitlement_manifest, target_sat): +def test_positive_logging_from_candlepin(module_org, module_sca_manifest, target_sat): """Check logging after manifest upload. :id: 8c06e501-52d7-4baf-903e-7de9caffb066 @@ -148,7 +147,7 @@ def test_positive_logging_from_candlepin(module_org, module_entitlement_manifest # get the number of lines in the source log before the test line_count_start = target_sat.execute(f'wc -l < {source_log}').stdout.strip('\n') # command for this test - with module_entitlement_manifest as manifest: + with module_sca_manifest as manifest: target_sat.upload_manifest(module_org.id, manifest, interface='CLI') # get the number of lines in the source log after the test line_count_end = target_sat.execute(f'wc -l < {source_log}').stdout.strip('\n') @@ -185,12 +184,12 @@ def test_positive_logging_from_dynflow(module_org, target_sat): POST_line_found = False source_log = '/var/log/foreman/production.log' test_logfile = '/var/tmp/logfile_dynflow' - product = entities.Product(organization=module_org).create() + product = target_sat.api.Product(organization=module_org).create() repo_name = gen_string('alpha') # get the number of lines in the source log before the test line_count_start = target_sat.execute(f'wc -l < {source_log}').stdout.strip('\n') # command for this test - new_repo = entities.Repository(name=repo_name, product=product).create() + new_repo = target_sat.api.Repository(name=repo_name, product=product).create() logger.info(f'Created Repo {new_repo.name} for dynflow log test') # get the number of lines in the source log after the test line_count_end = target_sat.execute(f'wc -l < {source_log}').stdout.strip('\n') diff --git a/tests/foreman/cli/test_oscap.py b/tests/foreman/cli/test_oscap.py index c198e5fa636..588fd2371ff 100644 --- a/tests/foreman/cli/test_oscap.py +++ b/tests/foreman/cli/test_oscap.py @@ -67,8 +67,7 @@ def test_positive_list_default_content_with_admin(self, module_target_sat): :CaseImportance: Medium """ scap_contents = [content['title'] for content in module_target_sat.cli.Scapcontent.list()] - for title in OSCAP_DEFAULT_CONTENT.values(): - assert title in scap_contents + assert f'Red Hat rhel{module_target_sat.os_version.major} default content' in scap_contents @pytest.mark.tier1 def test_negative_list_default_content_with_viewer_role( @@ -295,7 +294,9 @@ def test_positive_create_scap_content_with_valid_originalfile_name( @pytest.mark.parametrize('name', **parametrized(invalid_names_list())) @pytest.mark.tier1 def test_negative_create_scap_content_with_invalid_originalfile_name( - self, name, module_target_sat + self, + name, + module_target_sat, ): """Create scap-content with invalid original file name @@ -354,7 +355,10 @@ def test_negative_create_scap_content_without_dsfile(self, title, module_target_ module_target_sat.cli_factory.scapcontent({'title': title}) @pytest.mark.tier1 - def test_positive_update_scap_content_with_newtitle(self, module_target_sat): + def test_positive_update_scap_content_with_newtitle( + self, + module_target_sat, + ): """Update scap content title :id: 2c32e94a-237d-40b9-8a3b-fca2ef26fe79 @@ -920,7 +924,8 @@ def test_positive_update_scap_policy_with_content(self, scap_content, module_tar ) assert scap_policy['scap-content-id'] == scap_content["scap_id"] scap_id, scap_profile_id = self.fetch_scap_and_profile_id( - OSCAP_DEFAULT_CONTENT['rhel_firefox'], module_target_sat + OSCAP_DEFAULT_CONTENT[f'rhel{module_target_sat.os_version.major}_content'], + module_target_sat, ) module_target_sat.cli.Scappolicy.update( {'name': name, 'scap-content-id': scap_id, 'scap-content-profile-id': scap_profile_id} diff --git a/tests/foreman/cli/test_partitiontable.py b/tests/foreman/cli/test_partitiontable.py index 35e52cbbe65..51764ac8f0a 100644 --- a/tests/foreman/cli/test_partitiontable.py +++ b/tests/foreman/cli/test_partitiontable.py @@ -53,7 +53,7 @@ def test_positive_create_with_one_character_name(self, name, target_sat): strict=True, ) ) - ) + ), ) def test_positive_crud_with_name(self, name, new_name, module_target_sat): """Create, read, update and delete Partition Tables with different names @@ -86,23 +86,9 @@ def test_positive_create_with_content(self, module_target_sat): :CaseImportance: Critical """ content = 'Fake ptable' - ptable = module_target_sat.cli_factory.make_partition_table({'content': content}) - ptable_content = module_target_sat.cli.PartitionTable().dump({'id': ptable['id']}) - assert content in ptable_content - - @pytest.mark.tier1 - @pytest.mark.upgrade - def test_positive_create_with_content_length(self, module_target_sat): - """Create a Partition Table with content length more than 4096 chars - - :id: 59e6f9ef-85c2-4229-8831-00edb41b19f4 - - :expectedresults: Partition Table is created and has correct content - - :BZ: 1270181 - """ - content = gen_string('alpha', 5000) - ptable = module_target_sat.cli_factory.make_partition_table({'content': content}) + filename = gen_string('alpha', 10) + module_target_sat.execute(f'echo {content} > {filename}') + ptable = module_target_sat.cli_factory.make_partition_table({'file': filename}) ptable_content = module_target_sat.cli.PartitionTable().dump({'id': ptable['id']}) assert content in ptable_content diff --git a/tests/foreman/cli/test_registration.py b/tests/foreman/cli/test_registration.py index 212be27b756..34f3fb7f7b6 100644 --- a/tests/foreman/cli/test_registration.py +++ b/tests/foreman/cli/test_registration.py @@ -56,7 +56,7 @@ def test_host_registration_end_to_end( # Verify server.hostname and server.port from subscription-manager config assert module_target_sat.hostname == rhel_contenthost.subscription_config['server']['hostname'] - assert CLIENT_PORT == rhel_contenthost.subscription_config['server']['port'] + assert rhel_contenthost.subscription_config['server']['port'] == CLIENT_PORT # Update module_capsule_configured to include module_org/module_location module_target_sat.cli.Capsule.update( @@ -81,7 +81,7 @@ def test_host_registration_end_to_end( module_capsule_configured.hostname == rhel_contenthost.subscription_config['server']['hostname'] ) - assert CLIENT_PORT == rhel_contenthost.subscription_config['server']['port'] + assert rhel_contenthost.subscription_config['server']['port'] == CLIENT_PORT def test_upgrade_katello_ca_consumer_rpm( @@ -118,7 +118,7 @@ def test_upgrade_katello_ca_consumer_rpm( f'rpm -Uvh "http://{target_sat.hostname}/pub/{consumer_cert_name}-1.0-1.noarch.rpm"' ) # Check server URL is not Red Hat CDN's "subscription.rhsm.redhat.com" - assert 'subscription.rhsm.redhat.com' != vm.subscription_config['server']['hostname'] + assert vm.subscription_config['server']['hostname'] != 'subscription.rhsm.redhat.com' assert target_sat.hostname == vm.subscription_config['server']['hostname'] # Get consumer cert source file @@ -139,7 +139,7 @@ def test_upgrade_katello_ca_consumer_rpm( # Install new rpmbuild/RPMS/noarch/katello-ca-consumer-*-2.noarch.rpm assert vm.execute(f'yum install -y rpmbuild/RPMS/noarch/{new_consumer_cert_rpm}') # Check server URL is not Red Hat CDN's "subscription.rhsm.redhat.com" - assert 'subscription.rhsm.redhat.com' != vm.subscription_config['server']['hostname'] + assert vm.subscription_config['server']['hostname'] != 'subscription.rhsm.redhat.com' assert target_sat.hostname == vm.subscription_config['server']['hostname'] # Register as final check @@ -203,9 +203,9 @@ def test_positive_force_register_twice(module_ak_with_cv, module_org, rhel_conte assert f'The system has been registered with ID: {reg_id_new}' in str(result.stdout) assert reg_id_new != reg_id_old assert ( - target_sat.cli.Host.info({'name': rhel_contenthost.hostname})['subscription-information'][ - 'uuid' - ] + target_sat.cli.Host.info({'name': rhel_contenthost.hostname}, output_format='json')[ + 'subscription-information' + ]['uuid'] == reg_id_new ) diff --git a/tests/foreman/cli/test_remoteexecution.py b/tests/foreman/cli/test_remoteexecution.py index 2bf4b6a3ba4..c5a353395b7 100644 --- a/tests/foreman/cli/test_remoteexecution.py +++ b/tests/foreman/cli/test_remoteexecution.py @@ -16,7 +16,6 @@ import random from time import sleep -from broker import Broker from dateutil.relativedelta import FR, relativedelta from fauxfactory import gen_string import pytest @@ -24,8 +23,8 @@ from robottelo import constants from robottelo.config import settings -from robottelo.constants import FAKE_4_CUSTOM_PACKAGE, PRDS, REPOS, REPOSET -from robottelo.hosts import ContentHost +from robottelo.constants import FAKE_4_CUSTOM_PACKAGE +from robottelo.exceptions import CLIFactoryError from robottelo.utils import ohsnap from robottelo.utils.datafactory import filtered_datapoint, parametrized @@ -44,24 +43,6 @@ def valid_feature_names(): ] -@pytest.fixture -def fixture_sca_vmsetup(request, module_sca_manifest_org, target_sat): - """Create VM and register content host to Simple Content Access organization""" - if '_count' in request.param.keys(): - with Broker( - nick=request.param['nick'], - host_class=ContentHost, - _count=request.param['_count'], - ) as clients: - for client in clients: - client.configure_rex(satellite=target_sat, org=module_sca_manifest_org) - yield clients - else: - with Broker(nick=request.param['nick'], host_class=ContentHost) as client: - client.configure_rex(satellite=target_sat, org=module_sca_manifest_org) - yield client - - @pytest.fixture def infra_host(request, target_sat, module_capsule_configured): infra_hosts = {'target_sat': target_sat, 'module_capsule_configured': module_capsule_configured} @@ -157,28 +138,36 @@ def test_positive_run_default_job_template( @pytest.mark.pit_server @pytest.mark.rhel_ver_list([7, 8, 9]) def test_positive_run_job_effective_user(self, rex_contenthost, module_target_sat): - """Run default job template as effective user on a host + """Run default job template as effective user on a host, test ssh user as well :id: 0cd75cab-f699-47e6-94d3-4477d2a94bb7 - :BZ: 1451675, 1804685 + :BZ: 1451675, 1804685, 2258968 :expectedresults: Verify the job was successfully run under the - effective user identity on host + effective user identity on host, make sure the password is + used :parametrized: yes + + :customerscenario: true """ client = rex_contenthost # create a user on client via remote job + ssh_username = gen_string('alpha') + ssh_password = gen_string('alpha') username = gen_string('alpha') + password = gen_string('cjk') filename = gen_string('alpha') make_user_job = module_target_sat.cli_factory.job_invocation( { 'job-template': 'Run Command - Script Default', - 'inputs': f"command=useradd -m {username}", + 'inputs': f"command=useradd {ssh_username} -G wheel; echo {ssh_username}:{ssh_password} | chpasswd; useradd {username} -G wheel; echo {username}:{password} | chpasswd", 'search-query': f"name ~ {client.hostname}", + 'description-format': 'adding users', } ) + client.execute('echo "Defaults targetpw" >> /etc/sudoers') assert_job_invocation_result(module_target_sat, make_user_job['id'], client.hostname) # create a file as new user invocation_command = module_target_sat.cli_factory.job_invocation( @@ -186,7 +175,10 @@ def test_positive_run_job_effective_user(self, rex_contenthost, module_target_sa 'job-template': 'Run Command - Script Default', 'inputs': f"command=touch /home/{username}/{filename}", 'search-query': f"name ~ {client.hostname}", + 'ssh-user': f'{ssh_username}', + 'password': f'{ssh_password}', 'effective-user': f'{username}', + 'effective-user-password': f'{password}', } ) assert_job_invocation_result(module_target_sat, invocation_command['id'], client.hostname) @@ -196,6 +188,24 @@ def test_positive_run_job_effective_user(self, rex_contenthost, module_target_sa ) # assert the file is owned by the effective user assert username == result.stdout.strip('\n') + result = client.execute( + f'''stat -c '%G' /home/{username}/{filename}''', + ) + # assert the file is in the effective user's group + assert username == result.stdout.strip('\n') + # negative check for unspecified password + filename = gen_string('alpha') + with pytest.raises(CLIFactoryError): + invocation_command = module_target_sat.cli_factory.job_invocation( + { + 'job-template': 'Run Command - Script Default', + 'inputs': f"command=touch /home/{username}/{filename}", + 'search-query': f"name ~ {client.hostname}", + 'ssh-user': f'{ssh_username}', + 'password': f'{ssh_password}', + 'effective-user': f'{username}', + } + ) @pytest.mark.tier3 @pytest.mark.e2e @@ -230,10 +240,9 @@ def test_positive_run_custom_job_template(self, rex_contenthost, module_org, tar @pytest.mark.tier3 @pytest.mark.upgrade - @pytest.mark.no_containers @pytest.mark.rhel_ver_list([8]) def test_positive_run_default_job_template_multiple_hosts( - self, registered_hosts, module_target_sat + self, rex_contenthosts, module_target_sat ): """Run default job template against multiple hosts @@ -243,7 +252,7 @@ def test_positive_run_default_job_template_multiple_hosts( :parametrized: yes """ - clients = registered_hosts + clients = rex_contenthosts invocation_command = module_target_sat.cli_factory.job_invocation( { 'job-template': 'Run Command - Script Default', @@ -484,7 +493,6 @@ def test_positive_time_expressions(self, rex_contenthost, target_sat): today = datetime.today() hour = datetime.utcnow().hour last_day_of_month = monthrange(today.year, today.month)[1] - days_to = (2 - today.weekday()) % 7 # cronline uses https://github.com/floraison/fugit fugit_expressions = [ ['@yearly', f'{today.year + 1}/01/01 00:00:00'], @@ -504,11 +512,6 @@ def test_positive_time_expressions(self, rex_contenthost, target_sat): '@hourly', f'{(datetime.utcnow() + timedelta(hours=1)).strftime("%Y/%m/%d %H")}:00:00', ], - [ - '0 0 * * wed-fri', - f'{(today + timedelta(days=(days_to if days_to > 0 else 1))).strftime("%Y/%m/%d")} ' - '00:00:00', - ], # 23 mins after every other hour [ '23 0-23/2 * * *', @@ -628,392 +631,6 @@ def test_recurring_with_unreachable_host(self, module_target_sat, rhel_contentho assert cli.JobInvocation.info({'id': invocation.id})['failed'] != '0' -class TestAnsibleREX: - """Test class for remote execution via Ansible""" - - @pytest.mark.tier3 - @pytest.mark.upgrade - @pytest.mark.pit_client - @pytest.mark.pit_server - @pytest.mark.rhel_ver_list([7, 8, 9]) - def test_positive_run_effective_user_job(self, rex_contenthost, target_sat): - """Tests Ansible REX job having effective user runs successfully - - :id: a5fa20d8-c2bd-4bbf-a6dc-bf307b59dd8c - - :steps: - - 0. Create a VM and register to SAT and prepare for REX (ssh key) - - 1. Run Ansible Command job for the host to create a user - - 2. Run Ansible Command job using effective user - - 3. Check the job result at the host is done under that user - - :expectedresults: multiple asserts along the code - - :CaseAutomation: Automated - - :parametrized: yes - """ - client = rex_contenthost - # create a user on client via remote job - username = gen_string('alpha') - filename = gen_string('alpha') - make_user_job = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Run Command - Ansible Default', - 'inputs': f"command=useradd -m {username}", - 'search-query': f"name ~ {client.hostname}", - } - ) - assert_job_invocation_result(target_sat, make_user_job['id'], client.hostname) - # create a file as new user - invocation_command = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Run Command - Ansible Default', - 'inputs': f"command=touch /home/{username}/{filename}", - 'search-query': f"name ~ {client.hostname}", - 'effective-user': f'{username}', - } - ) - assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) - # check the file owner - result = client.execute( - f'''stat -c '%U' /home/{username}/{filename}''', - ) - # assert the file is owned by the effective user - assert username == result.stdout.strip('\n'), "file ownership mismatch" - - @pytest.mark.tier3 - @pytest.mark.upgrade - @pytest.mark.rhel_ver_list([8]) - def test_positive_run_reccuring_job(self, rex_contenthost, target_sat): - """Tests Ansible REX reccuring job runs successfully multiple times - - :id: 49b0d31d-58f9-47f1-aa5d-561a1dcb0d66 - - :steps: - - 0. Create a VM and register to SAT and prepare for REX (ssh key) - - 1. Run recurring Ansible Command job for the host - - 2. Check the multiple job results at the host - - :expectedresults: multiple asserts along the code - - :CaseAutomation: Automated - - :customerscenario: true - - :bz: 2129432 - - :parametrized: yes - """ - client = rex_contenthost - invocation_command = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Run Command - Ansible Default', - 'inputs': 'command=ls', - 'search-query': f"name ~ {client.hostname}", - 'cron-line': '* * * * *', # every minute - 'max-iteration': 2, # just two runs - } - ) - result = target_sat.cli.JobInvocation.info({'id': invocation_command['id']}) - sleep(150) - rec_logic = target_sat.cli.RecurringLogic.info({'id': result['recurring-logic-id']}) - assert rec_logic['state'] == 'finished' - assert rec_logic['iteration'] == '2' - # 2129432 - rec_logic_keys = rec_logic.keys() - assert 'action' in rec_logic_keys - assert 'last-occurrence' in rec_logic_keys - assert 'next-occurrence' in rec_logic_keys - assert 'state' in rec_logic_keys - assert 'purpose' in rec_logic_keys - assert 'iteration' in rec_logic_keys - assert 'iteration-limit' in rec_logic_keys - - @pytest.mark.tier3 - @pytest.mark.no_containers - def test_positive_run_concurrent_jobs(self, registered_hosts, target_sat): - """Tests Ansible REX concurent jobs without batch trigger - - :id: ad0f108c-03f2-49c7-8732-b1056570567b - - :steps: - - 0. Create 2 hosts, disable foreman_tasks_proxy_batch_trigger - - 1. Run Ansible Command job with concurrency-setting - - :expectedresults: multiple asserts along the code - - :CaseAutomation: Automated - - :customerscenario: true - - :BZ: 1817320 - - :parametrized: yes - """ - param_name = 'foreman_tasks_proxy_batch_trigger' - target_sat.cli.GlobalParameter().set({'name': param_name, 'value': 'false'}) - clients = registered_hosts - output_msgs = [] - invocation_command = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Run Command - Ansible Default', - 'inputs': 'command=ls', - 'search-query': f'name ~ {clients[0].hostname} or name ~ {clients[1].hostname}', - 'concurrency-level': 2, - } - ) - for vm in clients: - output_msgs.append( - 'host output from {}: {}'.format( - vm.hostname, - ' '.join( - target_sat.cli.JobInvocation.get_output( - {'id': invocation_command['id'], 'host': vm.hostname} - ) - ), - ) - ) - result = target_sat.cli.JobInvocation.info({'id': invocation_command['id']}) - assert result['success'] == '2', output_msgs - target_sat.cli.GlobalParameter().delete({'name': param_name}) - assert len(target_sat.cli.GlobalParameter().list({'search': param_name})) == 0 - - @pytest.mark.tier3 - @pytest.mark.no_containers - def test_positive_run_serial(self, registered_hosts, target_sat): - """Tests subtasks in a job run one by one when concurrency level set to 1 - - :id: 5ce39447-82d0-42df-81be-16ed3d67a2a4 - - :Setup: - 0. Create 2 hosts - - :steps: - - 0. Run a bash command job with concurrency level 1 - - :expectedresults: First subtask should run immediately, second one after the first one finishes - - :CaseAutomation: Automated - - :parametrized: yes - """ - hosts = registered_hosts - output_msgs = [] - template_file = f"/root/{gen_string('alpha')}.template" - target_sat.execute( - f"echo 'rm /root/test-<%= @host %>; echo $(date +%s) >> /root/test-<%= @host %>; sleep 120; echo $(date +%s) >> /root/test-<%= @host %>' > {template_file}" - ) - template = target_sat.cli.JobTemplate.create( - { - 'name': gen_string('alpha'), - 'file': template_file, - 'job-category': 'Commands', - 'provider-type': 'script', - } - ) - invocation = target_sat.cli_factory.job_invocation( - { - 'job-template': template['name'], - 'search-query': f'name ~ {hosts[0].hostname} or name ~ {hosts[1].hostname}', - 'concurrency-level': 1, - } - ) - for vm in hosts: - output_msgs.append( - 'host output from {}: {}'.format( - vm.hostname, - ' '.join( - target_sat.cli.JobInvocation.get_output( - {'id': invocation['id'], 'host': vm.hostname} - ) - ), - ) - ) - result = target_sat.cli.JobInvocation.info({'id': invocation['id']}) - assert result['success'] == '2', output_msgs - # assert for time diffs - file1 = hosts[0].execute('cat /root/test-$(hostname)').stdout - file2 = hosts[1].execute('cat /root/test-$(hostname)').stdout - file1_start, file1_end = map(int, file1.rstrip().split('\n')) - file2_start, file2_end = map(int, file2.rstrip().split('\n')) - if file1_start > file2_start: - file1_start, file1_end, file2_start, file2_end = ( - file2_start, - file2_end, - file1_start, - file1_end, - ) - assert file1_end - file1_start >= 120 - assert file2_end - file2_start >= 120 - assert file2_start >= file1_end # the jobs did NOT run concurrently - - @pytest.mark.tier3 - @pytest.mark.upgrade - @pytest.mark.e2e - @pytest.mark.no_containers - @pytest.mark.pit_server - @pytest.mark.rhel_ver_match('[^6].*') - @pytest.mark.skipif( - (not settings.robottelo.repos_hosting_url), reason='Missing repos_hosting_url' - ) - def test_positive_run_packages_and_services_job( - self, rhel_contenthost, module_org, module_ak_with_cv, target_sat - ): - """Tests Ansible REX job can install packages and start services - - :id: 47ed82fb-77ca-43d6-a52e-f62bae5d3a42 - - :steps: - - 0. Create a VM and register to SAT and prepare for REX (ssh key) - - 1. Run Ansible Package job for the host to install a package - - 2. Check the package is present at the host - - 3. Run Ansible Service job for the host to start a service - - 4. Check the service is started on the host - - :expectedresults: multiple asserts along the code - - :CaseAutomation: Automated - - :bz: 1872688, 1811166 - - :CaseImportance: Critical - - :customerscenario: true - - :parametrized: yes - """ - client = rhel_contenthost - packages = ['tapir'] - client.register( - module_org, - None, - module_ak_with_cv.name, - target_sat, - repo=settings.repos.yum_3.url, - ) - # install package - invocation_command = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Package Action - Ansible Default', - 'inputs': 'state=latest, name={}'.format(*packages), - 'search-query': f'name ~ {client.hostname}', - } - ) - assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) - result = client.run(f'rpm -q {" ".join(packages)}') - assert result.status == 0 - - # stop a service - service = "rsyslog" - invocation_command = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Service Action - Ansible Default', - 'inputs': f'state=stopped, name={service}', - 'search-query': f"name ~ {client.hostname}", - } - ) - assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) - result = client.execute(f'systemctl status {service}') - assert result.status == 3 - - # start it again - invocation_command = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Service Action - Ansible Default', - 'inputs': f'state=started, name={service}', - 'search-query': f'name ~ {client.hostname}', - } - ) - assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) - result = client.execute(f'systemctl status {service}') - assert result.status == 0 - - @pytest.mark.tier3 - @pytest.mark.parametrize( - 'fixture_sca_vmsetup', [{'nick': 'rhel8'}], ids=['rhel8'], indirect=True - ) - def test_positive_install_ansible_collection( - self, fixture_sca_vmsetup, module_sca_manifest_org, target_sat - ): - """Test whether Ansible collection can be installed via REX - - :steps: - 1. Upload a manifest. - 2. Enable and sync Ansible repository. - 3. Register content host to Satellite. - 4. Enable Ansible repo on content host. - 5. Install ansible package. - 6. Run REX job to install Ansible collection on content host. - - :id: ad25aee5-4ea3-4743-a301-1c6271856f79 - - :CaseComponent: Ansible - - :Team: Rocket - """ - # Configure repository to prepare for installing ansible on host - target_sat.cli.RepositorySet.enable( - { - 'basearch': 'x86_64', - 'name': REPOSET['rhae2.9_el8'], - 'organization-id': module_sca_manifest_org.id, - 'product': PRDS['rhae'], - 'releasever': '8', - } - ) - target_sat.cli.Repository.synchronize( - { - 'name': REPOS['rhae2.9_el8']['name'], - 'organization-id': module_sca_manifest_org.id, - 'product': PRDS['rhae'], - } - ) - client = fixture_sca_vmsetup - client.execute('subscription-manager refresh') - client.execute(f'subscription-manager repos --enable {REPOS["rhae2.9_el8"]["id"]}') - client.execute('dnf -y install ansible') - collection_job = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Ansible Collection - Install from Galaxy', - 'inputs': 'ansible_collections_list="oasis_roles.system"', - 'search-query': f'name ~ {client.hostname}', - } - ) - result = target_sat.cli.JobInvocation.info({'id': collection_job['id']}) - assert result['success'] == '1' - collection_path = client.execute('ls /etc/ansible/collections/ansible_collections').stdout - assert 'oasis_roles' in collection_path - - # Extend test with custom collections_path advanced input field - collection_job = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Ansible Collection - Install from Galaxy', - 'inputs': 'ansible_collections_list="oasis_roles.system", collections_path="~/"', - 'search-query': f'name ~ {client.hostname}', - } - ) - result = target_sat.cli.JobInvocation.info({'id': collection_job['id']}) - assert result['success'] == '1' - collection_path = client.execute('ls ~/ansible_collections').stdout - assert 'oasis_roles' in collection_path - - class TestRexUsers: """Tests related to remote execution users""" @@ -1308,15 +925,11 @@ def test_positive_run_job_on_host_converted_to_pull_provider( assert_job_invocation_result( module_target_sat, invocation_command['id'], rhel_contenthost.hostname ) - # check katello-agent runs along ygdrassil (SAT-1671) - result = rhel_contenthost.execute('systemctl status goferd') - assert result.status == 0, 'Failed to start goferd on client' - # run Ansible rex command to prove ssh provider works, remove katello-agent invocation_command = module_target_sat.cli_factory.job_invocation( { - 'job-template': 'Package Action - Ansible Default', - 'inputs': 'state=absent, name=katello-agent', + 'job-template': 'Remove Package - Katello Script Default', + 'inputs': 'package=katello-agent', 'search-query': f"name ~ {rhel_contenthost.hostname}", } ) @@ -1341,6 +954,110 @@ def test_positive_run_job_on_host_converted_to_pull_provider( ) result = module_target_sat.cli.JobInvocation.info({'id': invocation_command['id']}) + @pytest.mark.tier3 + @pytest.mark.no_containers + @pytest.mark.rhel_ver_match('[^6].*') + @pytest.mark.parametrize( + 'setting_update', + ['remote_execution_global_proxy=False'], + ids=["no_global_proxy"], + indirect=True, + ) + def test_positive_run_job_in_chosen_directory( + self, + module_org, + module_target_sat, + smart_proxy_location, + module_ak_with_cv, + module_capsule_configured_mqtt, + rhel_contenthost, + setting_update, + ): + """Run job on host registered to mqtt, check it honors run directory + + :id: d4ae37db-d3b6-41b3-bd98-48c29389e4c5 + + :expectedresults: Verify the job was successfully ran against the host registered to mqtt, in the correct directory + + :BZ: 2217079 + + :parametrized: yes + """ + client_repo = ohsnap.dogfood_repository( + settings.ohsnap, + product='client', + repo='client', + release='client', + os_release=rhel_contenthost.os_version.major, + ) + # Update module_capsule_configured_mqtt to include module_org/smart_proxy_location + module_target_sat.cli.Capsule.update( + { + 'name': module_capsule_configured_mqtt.hostname, + 'organization-ids': module_org.id, + 'location-ids': smart_proxy_location.id, + } + ) + # register host with pull provider rex + result = rhel_contenthost.register( + module_org, + smart_proxy_location, + module_ak_with_cv.name, + module_capsule_configured_mqtt, + setup_remote_execution_pull=True, + repo=client_repo.baseurl, + ignore_subman_errors=True, + force=True, + ) + + assert result.status == 0, f'Failed to register host: {result.stderr}' + # check mqtt client is running + result = rhel_contenthost.execute('systemctl status yggdrasild') + assert result.status == 0, f'Failed to start yggdrasil on client: {result.stderr}' + + # create a new directory and set in in yggdrasil + path = f'/{gen_string("alpha")}' + config_path_dir = '/etc/systemd/system/yggdrasild.service.d/' + config_path = f'{config_path_dir}/override.conf' + assert ( + rhel_contenthost.execute( + f'mkdir {path} && mount -t tmpfs tmpfs {path} && mkdir {config_path_dir} && echo -e "[Service]\nEnvironment=FOREMAN_YGG_WORKER_WORKDIR={path}" > {config_path} && systemctl daemon-reload && systemctl restart yggdrasild' + ).status + == 0 + ) + + # run rex command in the created directory + invocation_command = module_target_sat.cli_factory.job_invocation( + { + 'job-template': 'Run Command - Script Default', + 'inputs': 'command=printenv', + 'search-query': f"name ~ {rhel_contenthost.hostname}", + } + ) + assert_job_invocation_result( + module_target_sat, invocation_command['id'], rhel_contenthost.hostname + ) + assert ( + f'FOREMAN_YGG_WORKER_WORKDIR={path}' + in module_target_sat.cli.JobInvocation.get_output( + {'id': invocation_command['id'], 'host': rhel_contenthost.hostname} + ) + ) + + # remount the directory as noexec + rhel_contenthost.execute(f'mount -o remount,noexec {path}') + + # run rex command in the created directory again; + # it should fail; if it does not, it is probably not being run in that directory + with pytest.raises(CLIFactoryError): + invocation_command = module_target_sat.cli_factory.job_invocation( + { + 'job-template': 'Run Command - Script Default', + 'inputs': 'command=printenv', + 'search-query': f"name ~ {rhel_contenthost.hostname}", + } + ) + @pytest.mark.tier3 @pytest.mark.upgrade @pytest.mark.e2e diff --git a/tests/foreman/cli/test_reporttemplates.py b/tests/foreman/cli/test_reporttemplates.py index f778eaa802b..9c9bcf0e5a6 100644 --- a/tests/foreman/cli/test_reporttemplates.py +++ b/tests/foreman/cli/test_reporttemplates.py @@ -706,7 +706,7 @@ def test_positive_generate_ansible_template(module_target_sat): :CaseImportance: Medium """ settings = module_target_sat.cli.Settings.list({'search': 'name=ansible_inventory_template'}) - assert 1 == len(settings) + assert len(settings) == 1 template_name = settings[0]['value'] report_list = module_target_sat.cli.ReportTemplate.list() diff --git a/tests/foreman/cli/test_repositories.py b/tests/foreman/cli/test_repositories.py index cf6e10db5c0..fdbe9948b09 100644 --- a/tests/foreman/cli/test_repositories.py +++ b/tests/foreman/cli/test_repositories.py @@ -116,7 +116,7 @@ def test_positive_disable_rh_repo_with_basearch(module_target_sat, module_entitl disabled_repo = module_target_sat.cli.RepositorySet.disable( { 'basearch': DEFAULT_ARCHITECTURE, - 'name': REPOSET['kickstart']['rhel8'], + 'name': REPOSET['kickstart']['rhel8_bos'], 'product-id': repo.product.id, 'organization-id': module_entitlement_manifest_org.id, 'releasever': REPOS['kickstart']['rhel8_aps']['version'], diff --git a/tests/foreman/cli/test_repository.py b/tests/foreman/cli/test_repository.py index cc1a230bd96..6d612029d15 100644 --- a/tests/foreman/cli/test_repository.py +++ b/tests/foreman/cli/test_repository.py @@ -41,7 +41,6 @@ CUSTOM_FILE_REPO, CUSTOM_RPM_SHA, FAKE_5_YUM_REPO, - FAKE_YUM_DRPM_REPO, FAKE_YUM_MD5_REPO, FAKE_YUM_SRPM_REPO, ) @@ -652,7 +651,7 @@ def test_negative_update_to_invalid_download_policy(self, repo_options, repo, ta **parametrized( [ {'content-type': content_type, 'download-policy': 'on_demand'} - for content_type in REPO_TYPE.keys() + for content_type in REPO_TYPE if content_type != 'yum' if content_type != 'ostree' ] @@ -2007,8 +2006,7 @@ def test_positive_accessible_content_status( :CaseImportance: Critical """ - rhel7_contenthost.install_katello_ca(target_sat) - rhel7_contenthost.register_contenthost(module_org.label, module_ak_with_synced_repo['name']) + rhel7_contenthost.register(module_org, None, module_ak_with_synced_repo['name'], target_sat) assert rhel7_contenthost.subscribed rhel7_contenthost.run('yum repolist') access_log = target_sat.execute( @@ -2019,7 +2017,7 @@ def test_positive_accessible_content_status( @pytest.mark.tier2 @pytest.mark.parametrize( 'repo_options', - **parametrized([{'content_type': 'yum', 'url': CUSTOM_RPM_SHA}]), + **parametrized([{'content-type': 'yum', 'url': CUSTOM_RPM_SHA}]), indirect=True, ) def test_positive_sync_sha_repo(self, repo_options, module_target_sat): @@ -2044,7 +2042,7 @@ def test_positive_sync_sha_repo(self, repo_options, module_target_sat): @pytest.mark.tier2 @pytest.mark.parametrize( 'repo_options', - **parametrized([{'content_type': 'yum', 'url': CUSTOM_3RD_PARTY_REPO}]), + **parametrized([{'content-type': 'yum', 'url': CUSTOM_3RD_PARTY_REPO}]), indirect=True, ) def test_positive_sync_third_party_repo(self, repo_options, module_target_sat): @@ -2529,93 +2527,6 @@ def test_positive_sync_publish_promote_cv(self, repo, module_org, target_sat): assert lce['id'] in [lc['id'] for lc in cv['lifecycle-environments']] -@pytest.mark.skip_if_open("BZ:1682951") -class TestDRPMRepository: - """Tests specific to using repositories containing delta RPMs.""" - - @pytest.mark.tier2 - @pytest.mark.skip("Uses deprecated DRPM repository") - @pytest.mark.parametrize( - 'repo_options', **parametrized([{'url': FAKE_YUM_DRPM_REPO}]), indirect=True - ) - def test_positive_sync(self, repo, module_org, module_product, target_sat): - """Synchronize repository with DRPMs - - :id: a645966c-750b-40ef-a264-dc3bb632b9fd - - :parametrized: yes - - :expectedresults: drpms can be listed in repository - """ - target_sat.cli.Repository.synchronize({'id': repo['id']}) - result = target_sat.execute( - f"ls /var/lib/pulp/published/yum/https/repos/{module_org.label}/Library" - f"/custom/{module_product.label}/{repo['label']}/drpms/ | grep .drpm" - ) - assert result.status == 0 - assert result.stdout - - @pytest.mark.tier2 - @pytest.mark.skip("Uses deprecated DRPM repository") - @pytest.mark.parametrize( - 'repo_options', **parametrized([{'url': FAKE_YUM_DRPM_REPO}]), indirect=True - ) - def test_positive_sync_publish_cv(self, repo, module_org, module_product, target_sat): - """Synchronize repository with DRPMs, add repository to content view - and publish content view - - :id: 014bfc80-4622-422e-a0ec-755b1d9f845e - - :parametrized: yes - - :expectedresults: drpms can be listed in content view - """ - target_sat.cli.Repository.synchronize({'id': repo['id']}) - cv = target_sat.cli_factory.make_content_view({'organization-id': module_org.id}) - target_sat.cli.ContentView.add_repository({'id': cv['id'], 'repository-id': repo['id']}) - target_sat.cli.ContentView.publish({'id': cv['id']}) - result = target_sat.execute( - f"ls /var/lib/pulp/published/yum/https/repos/{module_org.label}/content_views/" - f"{cv['label']}/1.0/custom/{module_product.label}/{repo['label']}/drpms/ | grep .drpm" - ) - assert result.status == 0 - assert result.stdout - - @pytest.mark.tier2 - @pytest.mark.upgrade - @pytest.mark.skip("Uses deprecated DRPM repository") - @pytest.mark.parametrize( - 'repo_options', **parametrized([{'url': FAKE_YUM_DRPM_REPO}]), indirect=True - ) - def test_positive_sync_publish_promote_cv(self, repo, module_org, module_product, target_sat): - """Synchronize repository with DRPMs, add repository to content view, - publish and promote content view to lifecycle environment - - :id: a01cb12b-d388-4902-8532-714f4e28ec56 - - :parametrized: yes - - :expectedresults: drpms can be listed in content view in proper - lifecycle environment - """ - lce = target_sat.cli_factory.make_lifecycle_environment({'organization-id': module_org.id}) - target_sat.cli.Repository.synchronize({'id': repo['id']}) - cv = target_sat.cli_factory.make_content_view({'organization-id': module_org.id}) - target_sat.cli.ContentView.add_repository({'id': cv['id'], 'repository-id': repo['id']}) - target_sat.cli.ContentView.publish({'id': cv['id']}) - content_view = target_sat.cli.ContentView.info({'id': cv['id']}) - cvv = content_view['versions'][0] - target_sat.cli.ContentView.version_promote( - {'id': cvv['id'], 'to-lifecycle-environment-id': lce['id']} - ) - result = target_sat.execute( - f"ls /var/lib/pulp/published/yum/https/repos/{module_org.label}/{lce['label']}" - f"/{cv['label']}/custom/{module_product.label}/{repo['label']}/drpms/ | grep .drpm" - ) - assert result.status == 0 - assert result.stdout - - class TestFileRepository: """Specific tests for File Repositories""" @@ -2660,7 +2571,7 @@ def test_positive_upload_file_to_file_repo(self, repo_options, repo, target_sat) filesearch = entities.File().search( query={"search": f"name={RPM_TO_UPLOAD} and repository={repo['name']}"} ) - assert RPM_TO_UPLOAD == filesearch[0].name + assert filesearch[0].name == RPM_TO_UPLOAD @pytest.mark.tier1 @pytest.mark.upgrade diff --git a/tests/foreman/cli/test_rhcloud_inventory.py b/tests/foreman/cli/test_rhcloud_inventory.py index e127f6e7382..ed84fd465d7 100644 --- a/tests/foreman/cli/test_rhcloud_inventory.py +++ b/tests/foreman/cli/test_rhcloud_inventory.py @@ -1,12 +1,12 @@ """CLI tests for RH Cloud - Inventory, aka Insights Inventory Upload -:Requirement: RH Cloud - Inventory +:Requirement: RHCloud :CaseAutomation: Automated -:CaseComponent: RHCloud-Inventory +:CaseComponent: RHCloud -:Team: Platform +:Team: Phoenix-subscriptions :CaseImportance: High diff --git a/tests/foreman/cli/test_role.py b/tests/foreman/cli/test_role.py index 5eb4e8569f8..7ccdafdf4f1 100644 --- a/tests/foreman/cli/test_role.py +++ b/tests/foreman/cli/test_role.py @@ -296,7 +296,7 @@ def test_system_admin_role_end_to_end(self, target_sat): ).set({'name': "outofsync_interval", 'value': "32"}) sync_time = target_sat.cli.Settings.list({'search': 'name=outofsync_interval'})[0] # Asserts if the setting was updated successfully - assert '32' == sync_time['value'] + assert sync_time['value'] == '32' # Create another System Admin user using the first one system_admin = target_sat.cli.User.with_user( diff --git a/tests/foreman/cli/test_satellitesync.py b/tests/foreman/cli/test_satellitesync.py index dd80e98df8f..09ff7391a22 100644 --- a/tests/foreman/cli/test_satellitesync.py +++ b/tests/foreman/cli/test_satellitesync.py @@ -85,8 +85,7 @@ def export_import_cleanup_module(target_sat, module_org): @pytest.fixture def function_import_org(target_sat): """Creates an Organization for content import.""" - org = target_sat.api.Organization().create() - return org + return target_sat.api.Organization().create() @pytest.fixture @@ -152,14 +151,13 @@ def function_synced_rh_repo(request, target_sat, function_sca_manifest_org): # Update the download policy to 'immediate' and sync target_sat.cli.Repository.update({'download-policy': 'immediate', 'id': repo['id']}) target_sat.cli.Repository.synchronize({'id': repo['id']}, timeout='2h') - repo = target_sat.cli.Repository.info( + return target_sat.cli.Repository.info( { 'organization-id': function_sca_manifest_org.id, 'name': repo_dict['name'], 'product': repo_dict['product'], } ) - return repo @pytest.fixture @@ -1237,6 +1235,14 @@ def test_negative_import_incomplete_archive( {'organization-id': function_import_org_with_manifest.id, 'path': import_path} ) assert '1 subtask(s) failed' in error.value.message + target_sat.wait_for_tasks( + search_query=( + 'Actions::Katello::ContentView::Remove and ' + f'organization_id = {function_import_org_with_manifest.id}' + ), + max_tries=5, + poll_rate=10, + ) # Verify no content is imported and the import CV can be deleted imported_cv = target_sat.cli.ContentView.info( @@ -1579,6 +1585,7 @@ def test_positive_export_rerun_failed_import( assert len(importing_cvv) == 1 @pytest.mark.tier3 + @pytest.mark.skip_if_open("BZ:2262379") def test_postive_export_import_ansible_collection_repo( self, target_sat, @@ -1942,6 +1949,106 @@ def test_positive_export_incremental_syncable_check_content( f'{repomd_refs - drive_files}' ) + @pytest.mark.tier3 + def test_postive_export_import_with_long_name( + self, + target_sat, + config_export_import_settings, + export_import_cleanup_module, + module_org, + function_import_org, + ): + """Export and import content entities (product, repository, CV) with a long name. + + :id: 66d676ab-4e06-446b-b893-e236b26d37d9 + + :steps: + 1. Create product and repository with a long name, sync it. + 2. Export the repository, import it and verify the prod and repo names match the export. + 3. Verify the imported content matches the export. + 4. Create CV with a long name, add the repo and publish. + 5. Export the CV, import it and verify its name matches the export. + 6. Verify the imported content matches the export. + + :expectedresults: + 1. Exports and imports should succeed without any errors and names + and content of imported entities should match the export. + + :BZ: 2124275, 2053329 + + :customerscenario: true + """ + # Create product and repository with a long name, sync it. + product = target_sat.cli_factory.make_product( + {'name': gen_string('alpha', 128), 'organization-id': module_org.id} + ) + repo = target_sat.cli_factory.make_repository( + { + 'name': gen_string('alpha', 128), + 'content-type': 'yum', + 'download-policy': 'immediate', + 'organization-id': module_org.id, + 'product-id': product.id, + } + ) + target_sat.cli.Repository.synchronize({'id': repo.id}) + exported_packages = target_sat.cli.Package.list({'repository-id': repo.id}) + + # Export the repository, import it and verify the prod and repo names match the export. + export = target_sat.cli.ContentExport.completeRepository( + {'id': repo.id, 'organization-id': module_org.id} + ) + import_path = target_sat.move_pulp_archive(module_org, export['message']) + target_sat.cli.ContentImport.repository( + {'organization-id': function_import_org.id, 'path': import_path} + ) + import_repo = target_sat.cli.Repository.info( + { + 'organization-id': function_import_org.id, + 'name': repo.name, + 'product': product.name, + } + ) + + # Verify the imported content matches the export. + imported_packages = target_sat.cli.Package.list({'repository-id': import_repo['id']}) + assert imported_packages == exported_packages, 'Imported content does not match the export' + + # Create CV with a long name, add the repo and publish. + exporting_cv = target_sat.cli_factory.make_content_view( + {'name': gen_string('alpha', 128), 'organization-id': module_org.id} + ) + target_sat.cli.ContentView.add_repository( + { + 'id': exporting_cv.id, + 'organization-id': module_org.id, + 'repository-id': repo.id, + } + ) + target_sat.cli.ContentView.publish({'id': exporting_cv['id']}) + exporting_cv = target_sat.cli.ContentView.info({'id': exporting_cv['id']}) + assert len(exporting_cv['versions']) == 1 + exporting_cvv_id = exporting_cv['versions'][0]['id'] + + # Export the CV, import it and verify its name matches the export. + export = target_sat.cli.ContentExport.completeVersion( + {'id': exporting_cvv_id, 'organization-id': module_org.id} + ) + import_path = target_sat.move_pulp_archive(module_org, export['message']) + target_sat.cli.ContentImport.version( + {'organization-id': function_import_org.id, 'path': import_path} + ) + importing_cv = target_sat.cli.ContentView.info( + {'name': exporting_cv['name'], 'organization-id': function_import_org.id} + ) + assert len(importing_cv['versions']) == 1 + + # Verify the imported content matches the export. + imported_packages = target_sat.cli.Package.list( + {'content-view-version-id': importing_cv['versions'][0]['id']} + ) + assert exported_packages == imported_packages + class TestInterSatelliteSync: """Implements InterSatellite Sync tests in CLI""" @@ -2265,6 +2372,7 @@ def test_positive_custom_cdn_with_credential( meta_file = 'metadata.json' crt_file = 'source.crt' pub_dir = '/var/www/html/pub/repos' + request.addfinalizer(lambda: target_sat.execute(f'rm -rf {pub_dir}')) # Export the repository in syncable format and move it # to /var/www/html/pub/repos to mimic custom CDN. @@ -2281,7 +2389,6 @@ def test_positive_custom_cdn_with_credential( exp_dir = exp_dir[0].replace(meta_file, '') assert target_sat.execute(f'mv {exp_dir} {pub_dir}').status == 0 - request.addfinalizer(lambda: target_sat.execute(f'rm -rf {pub_dir}')) target_sat.execute(f'semanage fcontext -a -t httpd_sys_content_t "{pub_dir}(/.*)?"') target_sat.execute(f'restorecon -R {pub_dir}') diff --git a/tests/foreman/cli/test_sso.py b/tests/foreman/cli/test_sso.py index d8b772a936f..949e021ff48 100644 --- a/tests/foreman/cli/test_sso.py +++ b/tests/foreman/cli/test_sso.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: LDAP +:CaseComponent: Authentication :Team: Endeavour diff --git a/tests/foreman/cli/test_subnet.py b/tests/foreman/cli/test_subnet.py index 12272ed57c2..74a6e1727fc 100644 --- a/tests/foreman/cli/test_subnet.py +++ b/tests/foreman/cli/test_subnet.py @@ -199,8 +199,8 @@ def test_negative_update_attributes(request, options, module_target_sat): :CaseImportance: Medium """ subnet = module_target_sat.cli_factory.make_subnet() - options['id'] = subnet['id'] request.addfinalizer(lambda: module_target_sat.cli.Subnet.delete({'id': subnet['id']})) + options['id'] = subnet['id'] with pytest.raises(CLIReturnCodeError, match='Could not update the subnet:'): module_target_sat.cli.Subnet.update(options) # check - subnet is not updated @@ -223,8 +223,8 @@ def test_negative_update_address_pool(request, options, module_target_sat): :CaseImportance: Medium """ subnet = module_target_sat.cli_factory.make_subnet() - opts = {'id': subnet['id']} request.addfinalizer(lambda: module_target_sat.cli.Subnet.delete({'id': subnet['id']})) + opts = {'id': subnet['id']} # generate pool range from network address for key, val in options.items(): opts[key] = re.sub(r'\d+$', str(val), subnet['network-addr']) diff --git a/tests/foreman/cli/test_subscription.py b/tests/foreman/cli/test_subscription.py index c6d053a4659..c28ba08e61a 100644 --- a/tests/foreman/cli/test_subscription.py +++ b/tests/foreman/cli/test_subscription.py @@ -12,10 +12,12 @@ """ from fauxfactory import gen_string +from manifester import Manifester from nailgun import entities import pytest -from robottelo.constants import PRDS, REPOS, REPOSET +from robottelo.config import settings +from robottelo.constants import EXPIRED_MANIFEST, PRDS, REPOS, REPOSET, DataFile from robottelo.exceptions import CLIReturnCodeError pytestmark = [pytest.mark.run_in_one_thread] @@ -28,7 +30,7 @@ def golden_ticket_host_setup(request, module_sca_manifest_org, module_target_sat ) new_repo = module_target_sat.cli_factory.make_repository({'product-id': new_product['id']}) module_target_sat.cli.Repository.synchronize({'id': new_repo['id']}) - new_ak = module_target_sat.cli_factory.make_activation_key( + return module_target_sat.cli_factory.make_activation_key( { 'lifecycle-environment': 'Library', 'content-view': 'Default Organization View', @@ -36,7 +38,6 @@ def golden_ticket_host_setup(request, module_sca_manifest_org, module_target_sat 'auto-attach': False, } ) - return new_ak @pytest.mark.tier1 @@ -166,7 +167,7 @@ def test_positive_subscription_list(function_entitlement_manifest_org, module_ta {'organization-id': function_entitlement_manifest_org.id}, per_page=False ) for column in ['start-date', 'end-date']: - assert column in subscription_list[0].keys() + assert column in subscription_list[0] @pytest.mark.tier2 @@ -277,3 +278,44 @@ def test_positive_auto_attach_disabled_golden_ticket( with pytest.raises(CLIReturnCodeError) as context: target_sat.cli.Host.subscription_auto_attach({'host-id': host_id}) assert "This host's organization is in Simple Content Access mode" in str(context.value) + + +def test_negative_check_katello_reimport(target_sat, function_org): + """Verify katello:reimport trace should not fail with an TypeError + + :id: b7508a1c-7798-4649-83a3-cf94c7409c96 + + :steps: + 1. Import expired manifest & refresh + 2. Delete expired manifest + 3. Re-import new valid manifest & refresh + + :expectedresults: There should not be an error after reimport manifest + + :customerscenario: true + + :BZ: 2225534, 2253621 + """ + remote_path = f'/tmp/{EXPIRED_MANIFEST}' + target_sat.put(DataFile.EXPIRED_MANIFEST_FILE, remote_path) + # Import expired manifest & refresh + target_sat.cli.Subscription.upload({'organization-id': function_org.id, 'file': remote_path}) + with pytest.raises(CLIReturnCodeError): + target_sat.cli.Subscription.refresh_manifest({'organization-id': function_org.id}) + exec_val = target_sat.execute( + 'grep -i "Katello::HttpErrors::BadRequest: This Organization\'s subscription ' + 'manifest has expired. Please import a new manifest" /var/log/foreman/production.log' + ) + assert exec_val.status + # Delete expired manifest + target_sat.cli.Subscription.delete_manifest({'organization-id': function_org.id}) + # Re-import new manifest & refresh + manifester = Manifester(manifest_category=settings.manifest.golden_ticket) + manifest = manifester.get_manifest() + target_sat.upload_manifest(function_org.id, manifest.content) + ret_val = target_sat.cli.Subscription.refresh_manifest({'organization-id': function_org.id}) + assert 'Candlepin job status: SUCCESS' in ret_val + # Additional check, katello:reimport trace should not fail with TypeError + trace_output = target_sat.execute("foreman-rake katello:reimport --trace") + assert 'TypeError: no implicit conversion of String into Integer' not in trace_output.stdout + assert trace_output.status == 0 diff --git a/tests/foreman/cli/test_syncplan.py b/tests/foreman/cli/test_syncplan.py index 86f33b68ffb..129686447f4 100644 --- a/tests/foreman/cli/test_syncplan.py +++ b/tests/foreman/cli/test_syncplan.py @@ -580,7 +580,7 @@ def test_positive_synchronize_custom_products_future_sync_date(module_org, reque @pytest.mark.tier4 @pytest.mark.upgrade def test_positive_synchronize_rh_product_past_sync_date( - target_sat, function_entitlement_manifest_org, request + target_sat, function_sca_manifest_org, request ): """Create a sync plan with past datetime as a sync date, add a RH product and verify the product gets synchronized on the next sync @@ -594,7 +594,7 @@ def test_positive_synchronize_rh_product_past_sync_date( """ interval = 60 * 60 # 'hourly' sync interval in seconds delay = 2 * 60 - org = function_entitlement_manifest_org + org = function_sca_manifest_org target_sat.cli.RepositorySet.enable( { 'name': REPOSET['rhva6'], @@ -647,7 +647,7 @@ def test_positive_synchronize_rh_product_past_sync_date( @pytest.mark.tier4 @pytest.mark.upgrade def test_positive_synchronize_rh_product_future_sync_date( - target_sat, function_entitlement_manifest_org, request + target_sat, function_sca_manifest_org, request ): """Create a sync plan with sync date in a future and sync one RH product with it automatically. @@ -661,7 +661,7 @@ def test_positive_synchronize_rh_product_future_sync_date( cron_multiple = 5 # sync event is on every multiple of this value, starting from 00 mins delay = (cron_multiple) * 60 # delay for sync date in seconds guardtime = 180 # do not start test less than 2 mins before the next sync event - org = function_entitlement_manifest_org + org = function_sca_manifest_org target_sat.cli.RepositorySet.enable( { 'name': REPOSET['rhva6'], @@ -705,7 +705,7 @@ def test_positive_synchronize_rh_product_future_sync_date( # Verify product has not been synced yet with pytest.raises(AssertionError): validate_task_status(target_sat, repo['id'], org.id, max_tries=1) - validate_repo_content(repo, ['errata', 'packages'], after_sync=False) + validate_repo_content(target_sat, repo, ['errata', 'packages'], after_sync=False) # Wait the rest of expected time logger.info( f"Waiting {(delay * 4 / 5)} seconds to check product {product['name']}" @@ -714,7 +714,7 @@ def test_positive_synchronize_rh_product_future_sync_date( sleep(delay * 4 / 5) # Verify product was synced successfully validate_task_status(target_sat, repo['id'], org.id) - validate_repo_content(repo, ['errata', 'packages']) + validate_repo_content(target_sat, repo, ['errata', 'packages']) @pytest.mark.tier3 diff --git a/tests/foreman/cli/test_templatesync.py b/tests/foreman/cli/test_templatesync.py index 9959ae18459..b0cc31b2003 100644 --- a/tests/foreman/cli/test_templatesync.py +++ b/tests/foreman/cli/test_templatesync.py @@ -44,7 +44,7 @@ def setUpClass(self, module_target_sat): """ # Check all Downloadable templates exists - if not requests.get(FOREMAN_TEMPLATE_IMPORT_URL).status_code == 200: + if requests.get(FOREMAN_TEMPLATE_IMPORT_URL).status_code != 200: pytest.fail('The foreman templates git url is not accessible') # Download the Test Template in test running folder diff --git a/tests/foreman/cli/test_user.py b/tests/foreman/cli/test_user.py index c549b9e667a..3646e301db0 100644 --- a/tests/foreman/cli/test_user.py +++ b/tests/foreman/cli/test_user.py @@ -56,8 +56,8 @@ def roles_helper(): for role_name in valid_usernames_list() + include_list: yield module_target_sat.cli_factory.make_role({'name': role_name}) - stubbed_roles = {role['id']: role for role in roles_helper()} - return stubbed_roles + # return stubbed roles + return {role['id']: role for role in roles_helper()} @pytest.mark.parametrize('email', **parametrized(valid_emails_list())) @pytest.mark.tier2 diff --git a/tests/foreman/cli/test_usergroup.py b/tests/foreman/cli/test_usergroup.py index 6ad115d96f6..1c757b4e766 100644 --- a/tests/foreman/cli/test_usergroup.py +++ b/tests/foreman/cli/test_usergroup.py @@ -22,8 +22,7 @@ @pytest.fixture def function_user_group(target_sat): """Create new usergroup per each test""" - user_group = target_sat.cli_factory.usergroup() - return user_group + return target_sat.cli_factory.usergroup() @pytest.mark.tier1 @@ -230,9 +229,9 @@ def test_negative_automate_bz1437578(ldap_auth_source, function_user_group, modu } ) assert ( - 'Could not create external user group: ' + result == 'Could not create external user group: ' 'Name is not found in the authentication source' 'Name Domain Users is a special group in AD.' ' Unfortunately, we cannot obtain membership information' - ' from a LDAP search and therefore sync it.' == result + ' from a LDAP search and therefore sync it.' ) diff --git a/tests/foreman/conftest.py b/tests/foreman/conftest.py index 59616f233b7..1be6ba294e6 100644 --- a/tests/foreman/conftest.py +++ b/tests/foreman/conftest.py @@ -30,6 +30,10 @@ def pytest_collection_modifyitems(session, items, config): deselected_items = [] for item in items: + if any("manifest" in f for f in getattr(item, "fixturenames", ())): + item.add_marker("manifester") + if any("ldap" in f for f in getattr(item, "fixturenames", ())): + item.add_marker("ldap") # 1. Deselect tests marked with @pytest.mark.deselect # WONTFIX BZs makes test to be dynamically marked as deselect. deselect = item.get_closest_marker('deselect') @@ -66,7 +70,8 @@ def ui_session_record_property(request, record_property): test_file_path = request.node.fspath.strpath if any(directory in test_file_path for directory in test_directories): for fixture in request.node.fixturenames: - if request.fixturename != fixture: - if isinstance(request.getfixturevalue(fixture), Satellite): - sat = request.getfixturevalue(fixture) - sat.record_property = record_property + if request.fixturename != fixture and isinstance( + request.getfixturevalue(fixture), Satellite + ): + sat = request.getfixturevalue(fixture) + sat.record_property = record_property diff --git a/tests/foreman/data/expired-manifest.zip b/tests/foreman/data/expired-manifest.zip new file mode 100644 index 00000000000..8737ba42db2 Binary files /dev/null and b/tests/foreman/data/expired-manifest.zip differ diff --git a/tests/foreman/data/hammer_commands.json b/tests/foreman/data/hammer_commands.json index bf09ea854b1..648888532e7 100644 --- a/tests/foreman/data/hammer_commands.json +++ b/tests/foreman/data/hammer_commands.json @@ -746,7 +746,13 @@ "value": null }, { - "help": "Print help --------------------------------|-----|---------|----- | ALL | DEFAULT | THIN --------------------------------|-----|---------|----- | x | x | x | x | x | x | x | x | limit | x | x | attach | x | x | version | x | x | environment | x | x | view | x | x | collections/id | x | x | collections/name | x | x | overrides/content label | x | x | overrides/name | x | x | overrides/value | x | x | purpose/service level | x | x | purpose/purpose usage | x | x | purpose/purpose role | x | x | purpose/purpose addons | x | x | --------------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Show hosts associated to an activation key", + "name": "show-hosts", + "shortname": null, + "value": "BOOLEAN" + }, + { + "help": "Print help --------------------------------|-----|---------|----- | ALL | DEFAULT | THIN --------------------------------|-----|---------|----- | x | x | x | x | x | x | x | x | limit | x | x | attach | x | x | version | x | x | environment | x | x | view | x | x | hosts/id | x | x | hosts/name | x | x | collections/id | x | x | collections/name | x | x | overrides/content label | x | x | overrides/name | x | x | overrides/value | x | x | purpose/service level | x | x | purpose/purpose usage | x | x | purpose/purpose role | x | x | purpose/purpose addons | x | x | --------------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -1449,6 +1455,71 @@ } ], "subcommands": [ + { + "description": "Modify alternate content sources in bulk", + "name": "bulk", + "options": [ + { + "help": "Print help", + "name": "help", + "shortname": "h", + "value": null + } + ], + "subcommands": [ + { + "description": "Destroy alternate content sources", + "name": "destroy", + "options": [ + { + "help": "List of alternate content source ids", + "name": "ids", + "shortname": null, + "value": "LIST" + }, + { + "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "name": "help", + "shortname": "h", + "value": null + } + ], + "subcommands": [] + }, + { + "description": "Refresh alternate content sources", + "name": "refresh", + "options": [ + { + "help": "List of alternate content source ids", + "name": "ids", + "shortname": null, + "value": "LIST" + }, + { + "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "name": "help", + "shortname": "h", + "value": null + } + ], + "subcommands": [] + }, + { + "description": "Refresh all alternate content sources", + "name": "refresh-all", + "options": [ + { + "help": "Print help", + "name": "help", + "shortname": "h", + "value": null + } + ], + "subcommands": [] + } + ] + }, { "description": "Create an alternate content source to download content from during repository syncing. Note: alternate content sources are global and affect ALL sync actions on their capsules regardless of organization.", "name": "create", @@ -1612,7 +1683,7 @@ "value": "VALUE" }, { - "help": "Print help ------------------------------|-----|---------|----- | ALL | DEFAULT | THIN ------------------------------|-----|---------|----- | x | x | x | x | x | x | x | x | | x | x | url | x | x | type | x | x | content source type | x | x | username | x | x | Subpaths/ | x | x | Products/id | x | x | Products/organization id | x | x | Products/name | x | x | Products/label | x | x | proxies/id | x | x | proxies/name | x | x | proxies/url | x | x | proxies/download policy | x | x | ------------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help ------------------------------|-----|---------|----- | ALL | DEFAULT | THIN ------------------------------|-----|---------|----- | x | x | x | x | x | x | x | x | | x | x | url | x | x | type | x | x | content source type | x | x | username | x | x | ssl | x | x | ca cert/id | x | x | ca cert/name | x | x | client cert/id | x | x | client cert/name | x | x | client key/id | x | x | client key/name | x | x | Subpaths/ | x | x | Products/id | x | x | Products/organization id | x | x | Products/name | x | x | Products/label | x | x | proxies/id | x | x | proxies/name | x | x | proxies/url | x | x | proxies/download policy | x | x | ------------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -4108,7 +4179,7 @@ "value": "VALUE" }, { - "help": "Print help --------------------|-----|---------|----- | ALL | DEFAULT | THIN --------------------|-----|---------|----- | x | x | x at | x | x | name | x | x | x proxy name | x | x | name | x | x | | x | x | | x | x | | x | x | --------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string string Values: compliant, incompliant, inconclusive string Values: true, false string string integer integer string string string integer string Values: host, policy datetime string string integer text string string string integer string string datetime text string text string string", + "help": "Print help --------------------|-----|---------|----- | ALL | DEFAULT | THIN --------------------|-----|---------|----- | x | x | x at | x | x | name | x | x | x proxy name | x | x | name | x | x | | x | x | | x | x | | x | x | --------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string string Values: compliant, incompliant, inconclusive string Values: true, false string string integer integer string string string integer string Values: host, policy datetime lifecycle_environment string integer text string string string integer string string datetime text string text string string", "name": "help", "shortname": "h", "value": null @@ -4321,6 +4392,31 @@ ], "subcommands": [] }, + { + "description": "Authenticate against external source (IPA/PAM) with credentials", + "name": "basic-external", + "options": [ + { + "help": "Print help", + "name": "help", + "shortname": "h", + "value": null + }, + { + "help": "Password to access the remote system", + "name": "password", + "shortname": "p", + "value": "VALUE" + }, + { + "help": "Username to access the remote system you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "name": "username", + "shortname": "u", + "value": "VALUE" + } + ], + "subcommands": [] + }, { "description": "Negotiate the login credentials from the auth ticket (Kerberos)", "name": "negotiate", @@ -6230,7 +6326,7 @@ "value": null }, { - "help": "Print help -------------------------------------------------------------------|-----|-------- | ALL | DEFAULT -------------------------------------------------------------------|-----|-------- environments/name | x | x environments/organization | x | x environments/content views/name | x | x environments/content views/composite | x | x environments/content views/last published | x | x environments/content views/content/hosts | x | x environments/content views/content/products | x | x environments/content views/content/yum repos | x | x environments/content views/content/container image repos | x | x environments/content views/content/packages | x | x environments/content views/content/package groups | x | x environments/content views/content/errata | x | x -------------------------------------------------------------------|-----|-------- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help ---------------------------------------------------------------------------------|-----|-------- | ALL | DEFAULT ---------------------------------------------------------------------------------|-----|-------- environments/name | x | x environments/organization | x | x environments/content views/name | x | x environments/content views/composite | x | x environments/content views/last published | x | x environments/content views/repositories/repository id | x | x environments/content views/repositories/repository name | x | x environments/content views/repositories/content counts/warning | x | x environments/content views/repositories/content counts/packages | x | x environments/content views/repositories/content counts/srpms | x | x environments/content views/repositories/content counts/module streams | x | x environments/content views/repositories/content counts/package groups | x | x environments/content views/repositories/content counts/errata | x | x environments/content views/repositories/content counts/debian packages | x | x environments/content views/repositories/content counts/container tags | x | x environments/content views/repositories/content counts/container ma... | x | x environments/content views/repositories/content counts/container ma... | x | x environments/content views/repositories/content counts/files | x | x environments/content views/repositories/content counts/ansible coll... | x | x environments/content views/repositories/content counts/ostree refs | x | x environments/content views/repositories/content counts/python packages | x | x ---------------------------------------------------------------------------------|-----|-------- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -6293,6 +6389,37 @@ ], "subcommands": [] }, + { + "description": "Reclaim space from all On Demand repositories on a capsule", + "name": "reclaim-space", + "options": [ + { + "help": "Do not wait for the task", + "name": "async", + "shortname": null, + "value": null + }, + { + "help": "Id of the capsule", + "name": "id", + "shortname": null, + "value": "NUMBER" + }, + { + "help": "Name to search by", + "name": "name", + "shortname": null, + "value": "VALUE" + }, + { + "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "name": "help", + "shortname": "h", + "value": null + } + ], + "subcommands": [] + }, { "description": "Remove lifecycle environments from the capsule", "name": "remove-lifecycle-environment", @@ -6505,6 +6632,49 @@ } ], "subcommands": [] + }, + { + "description": "Update content counts for the capsule", + "name": "update-counts", + "options": [ + { + "help": "Do not wait for the task", + "name": "async", + "shortname": null, + "value": null + }, + { + "help": "Id of the capsule", + "name": "id", + "shortname": null, + "value": "NUMBER" + }, + { + "help": "Name to search by", + "name": "name", + "shortname": null, + "value": "VALUE" + }, + { + "help": "Organization name", + "name": "organization", + "shortname": null, + "value": "VALUE" + }, + { + "help": "Organization ID", + "name": "organization-id", + "shortname": null, + "value": "VALUE" + }, + { + "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "name": "help", + "shortname": "h", + "value": null + } + ], + "subcommands": [] } ] }, @@ -6816,7 +6986,7 @@ "value": null }, { - "help": "Print help -----------------|-----|---------|----- | ALL | DEFAULT | THIN -----------------|-----|---------|----- | x | x | x | x | x | x | x | x | | x | x | | x | x | | x | x | | x | x | Features/name | x | x | Features/version | x | x | Locations/ | x | x | Organizations/ | x | x | at | x | x | at | x | x | -----------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help -----------------|-----|---------|----- | ALL | DEFAULT | THIN -----------------|-----|---------|----- | x | x | x | x | x | x | x | x | | x | x | | x | x | | x | x | count | x | x | Features/name | x | x | Features/version | x | x | Locations/ | x | x | Organizations/ | x | x | at | x | x | at | x | x | -----------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -7523,7 +7693,7 @@ "value": null }, { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --interface: Libvirt: --interface: compute_type Possible values: bridge, network compute_bridge Name of interface according to type compute_model Possible values: virtio, rtl8139, ne2k_pci, pcnet, e1000 compute_network Libvirt instance network, e.g. default OpenStack: --interface: oVirt: --interface: compute_name Compute name, e.g. eth0 compute_network Select one of available networks for a cluster, must be an ID or a name compute_interface Interface type compute_vnic_profile Vnic Profile Rackspace: --interface: VMware: --interface: compute_type Type of the network adapter, for example one of: VirtualVmxnet3 VirtualE1000 See documentation center for your version of vSphere to find more details about available adapter types: https://www.vmware.com/support/pubs/ compute_network Network ID or Network Name from VMware AzureRM: --interface: compute_network Select one of available Azure Subnets, must be an ID compute_public_ip Public IP (None, Static, Dynamic) compute_private_ip Static Private IP (expressed as true or false) GCE: --interface:", + "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --interface: Libvirt: --interface: compute_type Possible values: bridge, network compute_bridge Name of interface according to type compute_model Possible values: virtio, rtl8139, ne2k_pci, pcnet, e1000 compute_network Libvirt instance network, e.g. default OpenStack: --interface: oVirt: --interface: compute_name Compute name, e.g. eth0 compute_network Select one of available networks for a cluster, must be an ID or a name compute_interface Interface type compute_vnic_profile Vnic Profile VMware: --interface: compute_type Type of the network adapter, for example one of: VirtualVmxnet3 VirtualE1000 See documentation center for your version of vSphere to find more details about available adapter types: https://www.vmware.com/support/pubs/ compute_network Network ID or Network Name from VMware AzureRM: --interface: compute_network Select one of available Azure Subnets, must be an ID compute_public_ip Public IP (None, Static, Dynamic) compute_private_ip Static Private IP (expressed as true or false) GCE: --interface:", "name": "help", "shortname": "h", "value": null @@ -7602,7 +7772,7 @@ "value": "KEY_VALUE_LIST" }, { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --volume: Libvirt: --volume: \u001b[1mpool_name\u001b[0m One of available storage pools \u001b[1mcapacity\u001b[0m String value, e.g. 10G allocation Initial allocation, e.g. 0G format_type Possible values: raw, qcow2 OpenStack: --volume: oVirt: --volume: size_gb Volume size in GB, integer value storage_domain ID or name of storage domain bootable Boolean, set 1 for bootable, only one volume can be bootable preallocate Boolean, set 1 to preallocate wipe_after_delete Boolean, set 1 to wipe disk after delete interface Disk interface name, must be ide, virto or virto_scsi Rackspace: --volume: VMware: --volume: name storage_pod Storage Pod ID from VMware datastore Datastore ID from VMware mode persistent/independent_persistent/independent_nonpersistent size_gb Integer number, volume size in GB thin true/false eager_zero true/false controller_key Associated SCSI controller key AzureRM: --volume: disk_size_gb Volume Size in GB (integer value) data_disk_caching Data Disk Caching (None, ReadOnly, ReadWrite) GCE: --volume: size_gb Volume size in GB, integer value", + "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --volume: Libvirt: --volume: \u001b[1mpool_name\u001b[0m One of available storage pools \u001b[1mcapacity\u001b[0m String value, e.g. 10G allocation Initial allocation, e.g. 0G format_type Possible values: raw, qcow2 OpenStack: --volume: oVirt: --volume: size_gb Volume size in GB, integer value storage_domain ID or name of storage domain bootable Boolean, set 1 for bootable, only one volume can be bootable preallocate Boolean, set 1 to preallocate wipe_after_delete Boolean, set 1 to wipe disk after delete interface Disk interface name, must be ide, virtio or virtio_scsi VMware: --volume: name storage_pod Storage Pod ID from VMware datastore Datastore ID from VMware mode persistent/independent_persistent/independent_nonpersistent size_gb Integer number, volume size in GB thin true/false eager_zero true/false controller_key Associated SCSI controller key AzureRM: --volume: disk_size_gb Volume Size in GB (integer value) data_disk_caching Data Disk Caching (None, ReadOnly, ReadWrite) GCE: --volume: size_gb Volume size in GB, integer value", "name": "help", "shortname": "h", "value": null @@ -7693,7 +7863,7 @@ "value": "KEY_VALUE_LIST" }, { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --volume: --interface: --compute-attributes: availability_zone flavor_id groups security_group_ids managed_ip Libvirt: --volume: \u001b[1mpool_name\u001b[0m One of available storage pools \u001b[1mcapacity\u001b[0m String value, e.g. 10G allocation Initial allocation, e.g. 0G format_type Possible values: raw, qcow2 --interface: compute_type Possible values: bridge, network compute_bridge Name of interface according to type compute_model Possible values: virtio, rtl8139, ne2k_pci, pcnet, e1000 compute_network Libvirt instance network, e.g. default --compute-attributes: \u001b[1mcpus\u001b[0m Number of CPUs \u001b[1mmemory\u001b[0m String, amount of memory, value in bytes cpu_mode Possible values: default, host-model, host-passthrough boot_order Device names to specify the boot order OpenStack: --volume: --interface: --compute-attributes: availability_zone boot_from_volume flavor_ref image_ref tenant_id security_groups network oVirt: --volume: size_gb Volume size in GB, integer value storage_domain ID or name of storage domain bootable Boolean, set 1 for bootable, only one volume can be bootable preallocate Boolean, set 1 to preallocate wipe_after_delete Boolean, set 1 to wipe disk after delete interface Disk interface name, must be ide, virto or virto_scsi --interface: compute_name Compute name, e.g. eth0 compute_network Select one of available networks for a cluster, must be an ID or a name compute_interface Interface type compute_vnic_profile Vnic Profile --compute-attributes: cluster ID or name of cluster to use template Hardware profile to use cores Integer value, number of cores sockets Integer value, number of sockets memory Amount of memory, integer value in bytes ha Boolean, set 1 to high availability display_type Possible values: VNC, SPICE keyboard_layout Possible values: ar, de-ch, es, fo, fr-ca, hu, ja, mk, no, pt-br, sv, da, en-gb, et, fr, fr-ch, is, lt, nl, pl, ru, th, de, en-us, fi, fr-be, hr, it, lv, nl-be, pt, sl, tr. Not usable if display type is SPICE. Rackspace: --volume: --interface: --compute-attributes: flavor_id VMware: --volume: name storage_pod Storage Pod ID from VMware datastore Datastore ID from VMware mode persistent/independent_persistent/independent_nonpersistent size_gb Integer number, volume size in GB thin true/false eager_zero true/false controller_key Associated SCSI controller key --interface: compute_type Type of the network adapter, for example one of: VirtualVmxnet3 VirtualE1000 See documentation center for your version of vSphere to find more details about available adapter types: https://www.vmware.com/support/pubs/ compute_network Network ID or Network Name from VMware --compute-attributes: \u001b[1mcluster\u001b[0m Cluster ID from VMware \u001b[1mcorespersocket\u001b[0m Number of cores per socket (applicable to hardware versions < 10 only) \u001b[1mcpus\u001b[0m CPU count \u001b[1mmemory_mb\u001b[0m Integer number, amount of memory in MB \u001b[1mpath\u001b[0m Path to folder \u001b[1mresource_pool\u001b[0m Resource Pool ID from VMware firmware automatic/bios/efi guest_id Guest OS ID form VMware hardware_version Hardware version ID from VMware memoryHotAddEnabled Must be a 1 or 0, lets you add memory resources while the machine is on cpuHotAddEnabled Must be a 1 or 0, lets you add CPU resources while the machine is on add_cdrom Must be a 1 or 0, Add a CD-ROM drive to the virtual machine annotation Annotation Notes scsi_controllers List with SCSI controllers definitions type - ID of the controller from VMware key - Key of the controller (e.g. 1000) boot_order Device names to specify the boot order AzureRM: --volume: disk_size_gb Volume Size in GB (integer value) data_disk_caching Data Disk Caching (None, ReadOnly, ReadWrite) --interface: compute_network Select one of available Azure Subnets, must be an ID compute_public_ip Public IP (None, Static, Dynamic) compute_private_ip Static Private IP (expressed as true or false) --compute-attributes: resource_group Existing Azure Resource Group of user vm_size VM Size, eg. Standard_A0 etc. username The Admin username password The Admin password platform OS type eg. Linux ssh_key_data SSH key for passwordless authentication os_disk_caching OS disk caching premium_os_disk Premium OS Disk, Boolean as 0 or 1 script_command Custom Script Command script_uris Comma seperated file URIs GCE: --volume: size_gb Volume size in GB, integer value --interface: --compute-attributes: machine_type network associate_external_ip", + "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --volume: --interface: --compute-attributes: availability_zone flavor_id groups security_group_ids managed_ip Libvirt: --volume: \u001b[1mpool_name\u001b[0m One of available storage pools \u001b[1mcapacity\u001b[0m String value, e.g. 10G allocation Initial allocation, e.g. 0G format_type Possible values: raw, qcow2 --interface: compute_type Possible values: bridge, network compute_bridge Name of interface according to type compute_model Possible values: virtio, rtl8139, ne2k_pci, pcnet, e1000 compute_network Libvirt instance network, e.g. default --compute-attributes: \u001b[1mcpus\u001b[0m Number of CPUs \u001b[1mmemory\u001b[0m String, amount of memory, value in bytes cpu_mode Possible values: default, host-model, host-passthrough boot_order Device names to specify the boot order OpenStack: --volume: --interface: --compute-attributes: availability_zone boot_from_volume flavor_ref image_ref tenant_id security_groups network oVirt: --volume: size_gb Volume size in GB, integer value storage_domain ID or name of storage domain bootable Boolean, set 1 for bootable, only one volume can be bootable preallocate Boolean, set 1 to preallocate wipe_after_delete Boolean, set 1 to wipe disk after delete interface Disk interface name, must be ide, virtio or virtio_scsi --interface: compute_name Compute name, e.g. eth0 compute_network Select one of available networks for a cluster, must be an ID or a name compute_interface Interface type compute_vnic_profile Vnic Profile --compute-attributes: cluster ID or name of cluster to use template Hardware profile to use cores Integer value, number of cores sockets Integer value, number of sockets memory Amount of memory, integer value in bytes ha Boolean, set 1 to high availability display_type Possible values: VNC, SPICE keyboard_layout Possible values: ar, de-ch, es, fo, fr-ca, hu, ja, mk, no, pt-br, sv, da, en-gb, et, fr, fr-ch, is, lt, nl, pl, ru, th, de, en-us, fi, fr-be, hr, it, lv, nl-be, pt, sl, tr. Not usable if display type is SPICE. VMware: --volume: name storage_pod Storage Pod ID from VMware datastore Datastore ID from VMware mode persistent/independent_persistent/independent_nonpersistent size_gb Integer number, volume size in GB thin true/false eager_zero true/false controller_key Associated SCSI controller key --interface: compute_type Type of the network adapter, for example one of: VirtualVmxnet3 VirtualE1000 See documentation center for your version of vSphere to find more details about available adapter types: https://www.vmware.com/support/pubs/ compute_network Network ID or Network Name from VMware --compute-attributes: \u001b[1mcluster\u001b[0m Cluster ID from VMware \u001b[1mcorespersocket\u001b[0m Number of cores per socket (applicable to hardware versions < 10 only) \u001b[1mcpus\u001b[0m CPU count \u001b[1mmemory_mb\u001b[0m Integer number, amount of memory in MB \u001b[1mpath\u001b[0m Path to folder \u001b[1mresource_pool\u001b[0m Resource Pool ID from VMware firmware automatic/bios/efi guest_id Guest OS ID form VMware hardware_version Hardware version ID from VMware memoryHotAddEnabled Must be a 1 or 0, lets you add memory resources while the machine is on cpuHotAddEnabled Must be a 1 or 0, lets you add CPU resources while the machine is on add_cdrom Must be a 1 or 0, Add a CD-ROM drive to the virtual machine annotation Annotation Notes scsi_controllers List with SCSI controllers definitions type - ID of the controller from VMware key - Key of the controller (e.g. 1000) boot_order Device names to specify the boot order AzureRM: --volume: disk_size_gb Volume Size in GB (integer value) data_disk_caching Data Disk Caching (None, ReadOnly, ReadWrite) --interface: compute_network Select one of available Azure Subnets, must be an ID compute_public_ip Public IP (None, Static, Dynamic) compute_private_ip Static Private IP (expressed as true or false) --compute-attributes: resource_group Existing Azure Resource Group of user vm_size VM Size, eg. Standard_A0 etc. username The Admin username password The Admin password platform OS type eg. Linux ssh_key_data SSH key for passwordless authentication os_disk_caching OS disk caching premium_os_disk Premium OS Disk, Boolean as 0 or 1 script_command Custom Script Command script_uris Comma seperated file URIs GCE: --volume: size_gb Volume size in GB, integer value --interface: --compute-attributes: machine_type network associate_external_ip", "name": "help", "shortname": "h", "value": null @@ -7942,7 +8112,7 @@ "value": "KEY_VALUE_LIST" }, { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --volume: --interface: --compute-attributes: availability_zone flavor_id groups security_group_ids managed_ip Libvirt: --volume: \u001b[1mpool_name\u001b[0m One of available storage pools \u001b[1mcapacity\u001b[0m String value, e.g. 10G allocation Initial allocation, e.g. 0G format_type Possible values: raw, qcow2 --interface: compute_type Possible values: bridge, network compute_bridge Name of interface according to type compute_model Possible values: virtio, rtl8139, ne2k_pci, pcnet, e1000 compute_network Libvirt instance network, e.g. default --compute-attributes: \u001b[1mcpus\u001b[0m Number of CPUs \u001b[1mmemory\u001b[0m String, amount of memory, value in bytes cpu_mode Possible values: default, host-model, host-passthrough boot_order Device names to specify the boot order OpenStack: --volume: --interface: --compute-attributes: availability_zone boot_from_volume flavor_ref image_ref tenant_id security_groups network oVirt: --volume: size_gb Volume size in GB, integer value storage_domain ID or name of storage domain bootable Boolean, set 1 for bootable, only one volume can be bootable preallocate Boolean, set 1 to preallocate wipe_after_delete Boolean, set 1 to wipe disk after delete interface Disk interface name, must be ide, virto or virto_scsi --interface: compute_name Compute name, e.g. eth0 compute_network Select one of available networks for a cluster, must be an ID or a name compute_interface Interface type compute_vnic_profile Vnic Profile --compute-attributes: cluster ID or name of cluster to use template Hardware profile to use cores Integer value, number of cores sockets Integer value, number of sockets memory Amount of memory, integer value in bytes ha Boolean, set 1 to high availability display_type Possible values: VNC, SPICE keyboard_layout Possible values: ar, de-ch, es, fo, fr-ca, hu, ja, mk, no, pt-br, sv, da, en-gb, et, fr, fr-ch, is, lt, nl, pl, ru, th, de, en-us, fi, fr-be, hr, it, lv, nl-be, pt, sl, tr. Not usable if display type is SPICE. Rackspace: --volume: --interface: --compute-attributes: flavor_id VMware: --volume: name storage_pod Storage Pod ID from VMware datastore Datastore ID from VMware mode persistent/independent_persistent/independent_nonpersistent size_gb Integer number, volume size in GB thin true/false eager_zero true/false controller_key Associated SCSI controller key --interface: compute_type Type of the network adapter, for example one of: VirtualVmxnet3 VirtualE1000 See documentation center for your version of vSphere to find more details about available adapter types: https://www.vmware.com/support/pubs/ compute_network Network ID or Network Name from VMware --compute-attributes: \u001b[1mcluster\u001b[0m Cluster ID from VMware \u001b[1mcorespersocket\u001b[0m Number of cores per socket (applicable to hardware versions < 10 only) \u001b[1mcpus\u001b[0m CPU count \u001b[1mmemory_mb\u001b[0m Integer number, amount of memory in MB \u001b[1mpath\u001b[0m Path to folder \u001b[1mresource_pool\u001b[0m Resource Pool ID from VMware firmware automatic/bios/efi guest_id Guest OS ID form VMware hardware_version Hardware version ID from VMware memoryHotAddEnabled Must be a 1 or 0, lets you add memory resources while the machine is on cpuHotAddEnabled Must be a 1 or 0, lets you add CPU resources while the machine is on add_cdrom Must be a 1 or 0, Add a CD-ROM drive to the virtual machine annotation Annotation Notes scsi_controllers List with SCSI controllers definitions type - ID of the controller from VMware key - Key of the controller (e.g. 1000) boot_order Device names to specify the boot order AzureRM: --volume: disk_size_gb Volume Size in GB (integer value) data_disk_caching Data Disk Caching (None, ReadOnly, ReadWrite) --interface: compute_network Select one of available Azure Subnets, must be an ID compute_public_ip Public IP (None, Static, Dynamic) compute_private_ip Static Private IP (expressed as true or false) --compute-attributes: resource_group Existing Azure Resource Group of user vm_size VM Size, eg. Standard_A0 etc. username The Admin username password The Admin password platform OS type eg. Linux ssh_key_data SSH key for passwordless authentication os_disk_caching OS disk caching premium_os_disk Premium OS Disk, Boolean as 0 or 1 script_command Custom Script Command script_uris Comma seperated file URIs GCE: --volume: size_gb Volume size in GB, integer value --interface: --compute-attributes: machine_type network associate_external_ip", + "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --volume: --interface: --compute-attributes: availability_zone flavor_id groups security_group_ids managed_ip Libvirt: --volume: \u001b[1mpool_name\u001b[0m One of available storage pools \u001b[1mcapacity\u001b[0m String value, e.g. 10G allocation Initial allocation, e.g. 0G format_type Possible values: raw, qcow2 --interface: compute_type Possible values: bridge, network compute_bridge Name of interface according to type compute_model Possible values: virtio, rtl8139, ne2k_pci, pcnet, e1000 compute_network Libvirt instance network, e.g. default --compute-attributes: \u001b[1mcpus\u001b[0m Number of CPUs \u001b[1mmemory\u001b[0m String, amount of memory, value in bytes cpu_mode Possible values: default, host-model, host-passthrough boot_order Device names to specify the boot order OpenStack: --volume: --interface: --compute-attributes: availability_zone boot_from_volume flavor_ref image_ref tenant_id security_groups network oVirt: --volume: size_gb Volume size in GB, integer value storage_domain ID or name of storage domain bootable Boolean, set 1 for bootable, only one volume can be bootable preallocate Boolean, set 1 to preallocate wipe_after_delete Boolean, set 1 to wipe disk after delete interface Disk interface name, must be ide, virtio or virtio_scsi --interface: compute_name Compute name, e.g. eth0 compute_network Select one of available networks for a cluster, must be an ID or a name compute_interface Interface type compute_vnic_profile Vnic Profile --compute-attributes: cluster ID or name of cluster to use template Hardware profile to use cores Integer value, number of cores sockets Integer value, number of sockets memory Amount of memory, integer value in bytes ha Boolean, set 1 to high availability display_type Possible values: VNC, SPICE keyboard_layout Possible values: ar, de-ch, es, fo, fr-ca, hu, ja, mk, no, pt-br, sv, da, en-gb, et, fr, fr-ch, is, lt, nl, pl, ru, th, de, en-us, fi, fr-be, hr, it, lv, nl-be, pt, sl, tr. Not usable if display type is SPICE. VMware: --volume: name storage_pod Storage Pod ID from VMware datastore Datastore ID from VMware mode persistent/independent_persistent/independent_nonpersistent size_gb Integer number, volume size in GB thin true/false eager_zero true/false controller_key Associated SCSI controller key --interface: compute_type Type of the network adapter, for example one of: VirtualVmxnet3 VirtualE1000 See documentation center for your version of vSphere to find more details about available adapter types: https://www.vmware.com/support/pubs/ compute_network Network ID or Network Name from VMware --compute-attributes: \u001b[1mcluster\u001b[0m Cluster ID from VMware \u001b[1mcorespersocket\u001b[0m Number of cores per socket (applicable to hardware versions < 10 only) \u001b[1mcpus\u001b[0m CPU count \u001b[1mmemory_mb\u001b[0m Integer number, amount of memory in MB \u001b[1mpath\u001b[0m Path to folder \u001b[1mresource_pool\u001b[0m Resource Pool ID from VMware firmware automatic/bios/efi guest_id Guest OS ID form VMware hardware_version Hardware version ID from VMware memoryHotAddEnabled Must be a 1 or 0, lets you add memory resources while the machine is on cpuHotAddEnabled Must be a 1 or 0, lets you add CPU resources while the machine is on add_cdrom Must be a 1 or 0, Add a CD-ROM drive to the virtual machine annotation Annotation Notes scsi_controllers List with SCSI controllers definitions type - ID of the controller from VMware key - Key of the controller (e.g. 1000) boot_order Device names to specify the boot order AzureRM: --volume: disk_size_gb Volume Size in GB (integer value) data_disk_caching Data Disk Caching (None, ReadOnly, ReadWrite) --interface: compute_network Select one of available Azure Subnets, must be an ID compute_public_ip Public IP (None, Static, Dynamic) compute_private_ip Static Private IP (expressed as true or false) --compute-attributes: resource_group Existing Azure Resource Group of user vm_size VM Size, eg. Standard_A0 etc. username The Admin username password The Admin password platform OS type eg. Linux ssh_key_data SSH key for passwordless authentication os_disk_caching OS disk caching premium_os_disk Premium OS Disk, Boolean as 0 or 1 script_command Custom Script Command script_uris Comma seperated file URIs GCE: --volume: size_gb Volume size in GB, integer value --interface: --compute-attributes: machine_type network associate_external_ip", "name": "help", "shortname": "h", "value": null @@ -8027,7 +8197,7 @@ "value": null }, { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --interface: Libvirt: --interface: compute_type Possible values: bridge, network compute_bridge Name of interface according to type compute_model Possible values: virtio, rtl8139, ne2k_pci, pcnet, e1000 compute_network Libvirt instance network, e.g. default OpenStack: --interface: oVirt: --interface: compute_name Compute name, e.g. eth0 compute_network Select one of available networks for a cluster, must be an ID or a name compute_interface Interface type compute_vnic_profile Vnic Profile Rackspace: --interface: VMware: --interface: compute_type Type of the network adapter, for example one of: VirtualVmxnet3 VirtualE1000 See documentation center for your version of vSphere to find more details about available adapter types: https://www.vmware.com/support/pubs/ compute_network Network ID or Network Name from VMware AzureRM: --interface: compute_network Select one of available Azure Subnets, must be an ID compute_public_ip Public IP (None, Static, Dynamic) compute_private_ip Static Private IP (expressed as true or false) GCE: --interface:", + "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --interface: Libvirt: --interface: compute_type Possible values: bridge, network compute_bridge Name of interface according to type compute_model Possible values: virtio, rtl8139, ne2k_pci, pcnet, e1000 compute_network Libvirt instance network, e.g. default OpenStack: --interface: oVirt: --interface: compute_name Compute name, e.g. eth0 compute_network Select one of available networks for a cluster, must be an ID or a name compute_interface Interface type compute_vnic_profile Vnic Profile VMware: --interface: compute_type Type of the network adapter, for example one of: VirtualVmxnet3 VirtualE1000 See documentation center for your version of vSphere to find more details about available adapter types: https://www.vmware.com/support/pubs/ compute_network Network ID or Network Name from VMware AzureRM: --interface: compute_network Select one of available Azure Subnets, must be an ID compute_public_ip Public IP (None, Static, Dynamic) compute_private_ip Static Private IP (expressed as true or false) GCE: --interface:", "name": "help", "shortname": "h", "value": null @@ -8112,7 +8282,7 @@ "value": "NUMBER" }, { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --volume: Libvirt: --volume: \u001b[1mpool_name\u001b[0m One of available storage pools \u001b[1mcapacity\u001b[0m String value, e.g. 10G allocation Initial allocation, e.g. 0G format_type Possible values: raw, qcow2 OpenStack: --volume: oVirt: --volume: size_gb Volume size in GB, integer value storage_domain ID or name of storage domain bootable Boolean, set 1 for bootable, only one volume can be bootable preallocate Boolean, set 1 to preallocate wipe_after_delete Boolean, set 1 to wipe disk after delete interface Disk interface name, must be ide, virto or virto_scsi Rackspace: --volume: VMware: --volume: name storage_pod Storage Pod ID from VMware datastore Datastore ID from VMware mode persistent/independent_persistent/independent_nonpersistent size_gb Integer number, volume size in GB thin true/false eager_zero true/false controller_key Associated SCSI controller key AzureRM: --volume: disk_size_gb Volume Size in GB (integer value) data_disk_caching Data Disk Caching (None, ReadOnly, ReadWrite) GCE: --volume: size_gb Volume size in GB, integer value", + "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --volume: Libvirt: --volume: \u001b[1mpool_name\u001b[0m One of available storage pools \u001b[1mcapacity\u001b[0m String value, e.g. 10G allocation Initial allocation, e.g. 0G format_type Possible values: raw, qcow2 OpenStack: --volume: oVirt: --volume: size_gb Volume size in GB, integer value storage_domain ID or name of storage domain bootable Boolean, set 1 for bootable, only one volume can be bootable preallocate Boolean, set 1 to preallocate wipe_after_delete Boolean, set 1 to wipe disk after delete interface Disk interface name, must be ide, virtio or virtio_scsi VMware: --volume: name storage_pod Storage Pod ID from VMware datastore Datastore ID from VMware mode persistent/independent_persistent/independent_nonpersistent size_gb Integer number, volume size in GB thin true/false eager_zero true/false controller_key Associated SCSI controller key AzureRM: --volume: disk_size_gb Volume Size in GB (integer value) data_disk_caching Data Disk Caching (None, ReadOnly, ReadWrite) GCE: --volume: size_gb Volume size in GB, integer value", "name": "help", "shortname": "h", "value": null @@ -10749,7 +10919,7 @@ "value": null }, { - "help": "Print help --------------------------------|-----|---------|----- | ALL | DEFAULT | THIN --------------------------------|-----|---------|----- | x | x | x | x | x | at | x | x | | x | x | status/applied | x | x | status/restarted | x | x | status/failed | x | x | status/restart failures | x | x | status/skipped | x | x | status/pending | x | x | metrics/config_retrieval | x | x | metrics/exec | x | x | metrics/file | x | x | metrics/package | x | x | metrics/service | x | x | metrics/user | x | x | metrics/yumrepo | x | x | metrics/filebucket | x | x | metrics/cron | x | x | metrics/total | x | x | Logs/resource | x | x | Logs/message | x | x | --------------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help --------------------------------|-----|---------|----- | ALL | DEFAULT | THIN --------------------------------|-----|---------|----- | x | x | x | x | x | at | x | x | | x | x | status/applied | x | x | status/restarted | x | x | status/failed | x | x | status/restart failures | x | x | status/skipped | x | x | status/pending | x | x | metrics/config retrieval | x | x | metrics/exec | x | x | metrics/file | x | x | metrics/package | x | x | metrics/service | x | x | metrics/user | x | x | metrics/yumrepo | x | x | metrics/filebucket | x | x | metrics/cron | x | x | metrics/total | x | x | Logs/resource | x | x | Logs/message | x | x | --------------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -11216,7 +11386,7 @@ "value": null }, { - "help": "Export formats. Choose syncable if content is to be imported via repository sync. Choose importable if content is to be imported via hammer content-import. Defaults to importable. Possible value(s): 'syncable', 'importable'", + "help": "Export formats.Choose syncable if the exported content needs to be in a yum format. This option is only available for yum, file repositories. Choose importable if the importing server uses the same version and exported content needs to be one of yum, file, ansible_collection, docker repositories. Possible value(s): 'syncable', 'importable'", "name": "format", "shortname": null, "value": "ENUM" @@ -11271,7 +11441,7 @@ "value": "NUMBER" }, { - "help": "Export formats. Choose syncable if content is to be imported via repository sync. Choose importable if content is to be imported via hammer content-import. Defaults to importable. Possible value(s): 'syncable', 'importable'", + "help": "Export formats.Choose syncable if the exported content needs to be in a yum format. This option is only available for yum, file repositories. Choose importable if the importing server uses the same version and exported content needs to be one of yum, file, ansible_collection, docker repositories. Possible value(s): 'syncable', 'importable'", "name": "format", "shortname": null, "value": "ENUM" @@ -11374,7 +11544,7 @@ "value": null }, { - "help": "Export formats. Choose syncable if content is to be imported via repository sync. Choose importable if content is to be imported via hammer content-import. Defaults to importable. Possible value(s): 'syncable', 'importable'", + "help": "Export formats.Choose syncable if the exported content needs to be in a yum format. This option is only available for yum, file repositories. Choose importable if the importing server uses the same version and exported content needs to be one of yum, file, ansible_collection, docker repositories. Possible value(s): 'syncable', 'importable'", "name": "format", "shortname": null, "value": "ENUM" @@ -11528,6 +11698,12 @@ "shortname": null, "value": null }, + { + "help": "Export formats.Choose syncable if the exported content needs to be in a yum format. This option is only available for yum, file repositories. Choose importable if the importing server uses the same version and exported content needs to be one of yum, file, ansible_collection, docker repositories. Possible value(s): 'syncable', 'importable'", + "name": "format", + "shortname": null, + "value": "ENUM" + }, { "help": "Export history id used for incremental export. If not provided the most recent export history will be used.", "name": "from-history-id", @@ -11583,6 +11759,12 @@ "shortname": null, "value": "NUMBER" }, + { + "help": "Export formats.Choose syncable if the exported content needs to be in a yum format. This option is only available for yum, file repositories. Choose importable if the importing server uses the same version and exported content needs to be one of yum, file, ansible_collection, docker repositories. Possible value(s): 'syncable', 'importable'", + "name": "format", + "shortname": null, + "value": "ENUM" + }, { "help": "Export history id used for incremental export. If not provided the most recent export history will be used.", "name": "from-history-id", @@ -11686,6 +11868,12 @@ "shortname": null, "value": null }, + { + "help": "Export formats.Choose syncable if the exported content needs to be in a yum format. This option is only available for yum, file repositories. Choose importable if the importing server uses the same version and exported content needs to be one of yum, file, ansible_collection, docker repositories. Possible value(s): 'syncable', 'importable'", + "name": "format", + "shortname": null, + "value": "ENUM" + }, { "help": "Export history id used for incremental export. If not provided the most recent export history will be used.", "name": "from-history-id", @@ -14335,7 +14523,7 @@ "value": "LIST" }, { - "help": "Print help ----------------|-----|---------|----- | ALL | DEFAULT | THIN ----------------|-----|---------|----- view id | x | x | x | x | x | x | x | x | | x | x | published | x | x | ids | x | x | ----------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string boolean boolean integer string string integer", + "help": "Print help ----------------|-----|---------|----- | ALL | DEFAULT | THIN ----------------|-----|---------|----- view id | x | x | x | x | x | x | x | x | | x | x | published | x | x | ids | x | x | ----------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string boolean string boolean integer string string integer", "name": "help", "shortname": "h", "value": null @@ -14419,6 +14607,12 @@ "shortname": null, "value": "VALUE" }, + { + "help": "Check audited changes and proceed only if content or filters have changed since last publish", + "name": "publish-only-if-needed", + "shortname": null, + "value": "BOOLEAN" + }, { "help": "Specify the list of units in each repo", "name": "repos-units", @@ -15148,6 +15342,12 @@ "shortname": null, "value": "NUMBER" }, + { + "help": "Whether or not to return filters applied to the content view version", + "name": "include-applied-filters", + "shortname": null, + "value": "BOOLEAN" + }, { "help": "VALUE/NUMBER Name/Id of associated lifecycle environment", "name": "lifecycle-environment", @@ -15191,7 +15391,7 @@ "value": "VALUE" }, { - "help": "Print help -----------------------------|-----|---------|----- | ALL | DEFAULT | THIN -----------------------------|-----|---------|----- | x | x | x | x | x | | x | x | x | x | x | view id | x | x | view name | x | x | view label | x | x | environments/id | x | x | environments/name | x | x | environments/label | x | x | Repositories/id | x | x | Repositories/name | x | x | Repositories/label | x | x | -----------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help ---------------------------------------------|-----|---------|----- | ALL | DEFAULT | THIN ---------------------------------------------|-----|---------|----- | x | x | x | x | x | | x | x | x | x | x | view id | x | x | view name | x | x | view label | x | x | environments/id | x | x | environments/name | x | x | environments/label | x | x | Repositories/id | x | x | Repositories/name | x | x | Repositories/label | x | x | applied filters | x | x | filters/id | x | x | filters/name | x | x | filters/type | x | x | filters/inclusion | x | x | filters/original packages | x | x | filters/original module streams | x | x | filters/rules/id | x | x | filters/rules/name | x | x | filters/rules/uuid | x | x | filters/rules/module stream id | x | x | filters/rules/types/ | x | x | filters/rules/architecture | x | x | filters/rules/content view filter id | x | x | filters/rules/errata id | x | x | filters/rules/date type | x | x | filters/rules/start date | x | x | filters/rules/end date | x | x | solving | x | x | ---------------------------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -15239,12 +15439,24 @@ "shortname": null, "value": "LIST" }, + { + "help": "Filter content view versions that contain the file", + "name": "file-id", + "shortname": null, + "value": "NUMBER" + }, { "help": "Whether or not to show all results", "name": "full-result", "shortname": null, "value": "BOOLEAN" }, + { + "help": "Whether or not to return filters applied to the content view version", + "name": "include-applied-filters", + "shortname": null, + "value": "BOOLEAN" + }, { "help": "VALUE/NUMBER Filter versions by environment", "name": "lifecycle-environment", @@ -15257,6 +15469,12 @@ "shortname": null, "value": null }, + { + "help": "Filter out default content views", + "name": "nondefault", + "shortname": null, + "value": "BOOLEAN" + }, { "help": "Sort field and order, eg. 'id DESC'", "name": "order", @@ -15452,7 +15670,7 @@ "value": null }, { - "help": "Force metadata regeneration to proceed. Dangerous when repositories use the 'Complete Mirroring' mirroring policy", + "help": "Force metadata regeneration to proceed. Dangerous operation when version has repositories with the 'Complete Mirroring' mirroring policy", "name": "force", "shortname": null, "value": "BOOLEAN" @@ -18486,6 +18704,18 @@ "shortname": null, "value": "VALUE" }, + { + "help": "VALUE/NUMBER (--environment-id is deprecated: Use --lifecycle-environment-id instead)", + "name": "environment", + "shortname": null, + "value": null + }, + { + "help": "VALUE/NUMBER (--environment-id is deprecated: Use --lifecycle-environment-id instead)", + "name": "environment-id", + "shortname": null, + "value": null + }, { "help": "Return errata that are applicable to one or more hosts (defaults to true if host_id is specified)", "name": "errata-restrict-applicable", @@ -18523,10 +18753,16 @@ "value": null }, { - "help": "Environment id", + "help": "VALUE/NUMBER Environment name/id", + "name": "lifecycle-environment", + "shortname": null, + "value": null + }, + { + "help": "VALUE/NUMBER Environment name/id", "name": "lifecycle-environment-id", "shortname": null, - "value": "NUMBER" + "value": null }, { "help": "Sort field and order, eg. 'id DESC'", @@ -21265,7 +21501,7 @@ "value": "KEY_VALUE_LIST" }, { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string parameters accept format defined by its schema (bold are required; <> contains acceptable type; [] contains acceptable value): \"\u001b[1mname\u001b[0m=\\,\u001b[1mvalue\u001b[0m=\\,parameter_type=[string|boolean|integer|real|array|hash|yaml|json]\\,hidden_value=[true|false|1|0], ... \" \"product_id=\\,product_name=\\,arch=\\,version=, ... \" mac ip Possible values: interface, bmc, bond, bridge name subnet_id domain_id identifier true/false true/false, each managed hosts needs to have one primary interface. true/false true/false virtual=true: tag VLAN tag, this attribute has precedence over the subnet VLAN ID. Only for virtual interfaces. attached_to Identifier of the interface to which this interface belongs, e.g. eth1. type=bond: mode Possible values: balance-rr, active-backup, balance-xor, broadcast, 802.3ad, balance-tlb, balance-alb attached_devices Identifiers of slave interfaces, e.g. [eth1,eth2] bond_options type=bmc: provider always IPMI username password \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --volume: --interface: --compute-attributes: availability_zone flavor_id groups security_group_ids managed_ip Libvirt: --volume: \u001b[1mpool_name\u001b[0m One of available storage pools \u001b[1mcapacity\u001b[0m String value, e.g. 10G allocation Initial allocation, e.g. 0G format_type Possible values: raw, qcow2 --interface: compute_type Possible values: bridge, network compute_bridge Name of interface according to type compute_model Possible values: virtio, rtl8139, ne2k_pci, pcnet, e1000 compute_network Libvirt instance network, e.g. default --compute-attributes: \u001b[1mcpus\u001b[0m Number of CPUs \u001b[1mmemory\u001b[0m String, amount of memory, value in bytes cpu_mode Possible values: default, host-model, host-passthrough boot_order Device names to specify the boot order start Boolean (expressed as 0 or 1), whether to start the machine or not OpenStack: --volume: --interface: --compute-attributes: availability_zone boot_from_volume flavor_ref image_ref tenant_id security_groups network oVirt: --volume: size_gb Volume size in GB, integer value storage_domain ID or name of storage domain bootable Boolean, set 1 for bootable, only one volume can be bootable preallocate Boolean, set 1 to preallocate wipe_after_delete Boolean, set 1 to wipe disk after delete interface Disk interface name, must be ide, virto or virto_scsi --interface: compute_name Compute name, e.g. eth0 compute_network Select one of available networks for a cluster, must be an ID or a name compute_interface Interface type compute_vnic_profile Vnic Profile --compute-attributes: cluster ID or name of cluster to use template Hardware profile to use cores Integer value, number of cores sockets Integer value, number of sockets memory Amount of memory, integer value in bytes ha Boolean, set 1 to high availability display_type Possible values: VNC, SPICE keyboard_layout Possible values: ar, de-ch, es, fo, fr-ca, hu, ja, mk, no, pt-br, sv, da, en-gb, et, fr, fr-ch, is, lt, nl, pl, ru, th, de, en-us, fi, fr-be, hr, it, lv, nl-be, pt, sl, tr. Not usable if display type is SPICE. start Boolean, set 1 to start the vm Rackspace: --volume: --interface: --compute-attributes: flavor_id VMware: --volume: name storage_pod Storage Pod ID from VMware datastore Datastore ID from VMware mode persistent/independent_persistent/independent_nonpersistent size_gb Integer number, volume size in GB thin true/false eager_zero true/false controller_key Associated SCSI controller key --interface: compute_type Type of the network adapter, for example one of: VirtualVmxnet3 VirtualE1000 See documentation center for your version of vSphere to find more details about available adapter types: https://www.vmware.com/support/pubs/ compute_network Network ID or Network Name from VMware --compute-attributes: \u001b[1mcluster\u001b[0m Cluster ID from VMware \u001b[1mcorespersocket\u001b[0m Number of cores per socket (applicable to hardware versions < 10 only) \u001b[1mcpus\u001b[0m CPU count \u001b[1mmemory_mb\u001b[0m Integer number, amount of memory in MB \u001b[1mpath\u001b[0m Path to folder \u001b[1mresource_pool\u001b[0m Resource Pool ID from VMware firmware automatic/bios/efi guest_id Guest OS ID form VMware hardware_version Hardware version ID from VMware memoryHotAddEnabled Must be a 1 or 0, lets you add memory resources while the machine is on cpuHotAddEnabled Must be a 1 or 0, lets you add CPU resources while the machine is on add_cdrom Must be a 1 or 0, Add a CD-ROM drive to the virtual machine annotation Annotation Notes scsi_controllers List with SCSI controllers definitions type - ID of the controller from VMware key - Key of the controller (e.g. 1000) boot_order Device names to specify the boot order start Must be a 1 or 0, whether to start the machine or not AzureRM: --volume: disk_size_gb Volume Size in GB (integer value) data_disk_caching Data Disk Caching (None, ReadOnly, ReadWrite) --interface: compute_network Select one of available Azure Subnets, must be an ID compute_public_ip Public IP (None, Static, Dynamic) compute_private_ip Static Private IP (expressed as true or false) --compute-attributes: resource_group Existing Azure Resource Group of user vm_size VM Size, eg. Standard_A0 etc. username The Admin username password The Admin password platform OS type eg. Linux ssh_key_data SSH key for passwordless authentication os_disk_caching OS disk caching premium_os_disk Premium OS Disk, Boolean as 0 or 1 script_command Custom Script Command script_uris Comma seperated file URIs GCE: --volume: size_gb Volume size in GB, integer value --interface: --compute-attributes: machine_type network associate_external_ip", + "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string parameters accept format defined by its schema (bold are required; <> contains acceptable type; [] contains acceptable value): \"\u001b[1mname\u001b[0m=\\,\u001b[1mvalue\u001b[0m=\\,parameter_type=[string|boolean|integer|real|array|hash|yaml|json]\\,hidden_value=[true|false|1|0], ... \" \"product_id=\\,product_name=\\,arch=\\,version=, ... \" mac ip Possible values: interface, bmc, bond, bridge name subnet_id domain_id identifier true/false true/false, each managed hosts needs to have one primary interface. true/false true/false virtual=true: tag VLAN tag, this attribute has precedence over the subnet VLAN ID. Only for virtual interfaces. attached_to Identifier of the interface to which this interface belongs, e.g. eth1. type=bond: mode Possible values: balance-rr, active-backup, balance-xor, broadcast, 802.3ad, balance-tlb, balance-alb attached_devices Identifiers of slave interfaces, e.g. [eth1,eth2] bond_options type=bmc: provider always IPMI username password \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --volume: --interface: --compute-attributes: availability_zone flavor_id groups security_group_ids managed_ip Libvirt: --volume: \u001b[1mpool_name\u001b[0m One of available storage pools \u001b[1mcapacity\u001b[0m String value, e.g. 10G allocation Initial allocation, e.g. 0G format_type Possible values: raw, qcow2 --interface: compute_type Possible values: bridge, network compute_bridge Name of interface according to type compute_model Possible values: virtio, rtl8139, ne2k_pci, pcnet, e1000 compute_network Libvirt instance network, e.g. default --compute-attributes: \u001b[1mcpus\u001b[0m Number of CPUs \u001b[1mmemory\u001b[0m String, amount of memory, value in bytes cpu_mode Possible values: default, host-model, host-passthrough boot_order Device names to specify the boot order start Boolean (expressed as 0 or 1), whether to start the machine or not OpenStack: --volume: --interface: --compute-attributes: availability_zone boot_from_volume flavor_ref image_ref tenant_id security_groups network oVirt: --volume: size_gb Volume size in GB, integer value storage_domain ID or name of storage domain bootable Boolean, set 1 for bootable, only one volume can be bootable preallocate Boolean, set 1 to preallocate wipe_after_delete Boolean, set 1 to wipe disk after delete interface Disk interface name, must be ide, virtio or virtio_scsi --interface: compute_name Compute name, e.g. eth0 compute_network Select one of available networks for a cluster, must be an ID or a name compute_interface Interface type compute_vnic_profile Vnic Profile --compute-attributes: cluster ID or name of cluster to use template Hardware profile to use cores Integer value, number of cores sockets Integer value, number of sockets memory Amount of memory, integer value in bytes ha Boolean, set 1 to high availability display_type Possible values: VNC, SPICE keyboard_layout Possible values: ar, de-ch, es, fo, fr-ca, hu, ja, mk, no, pt-br, sv, da, en-gb, et, fr, fr-ch, is, lt, nl, pl, ru, th, de, en-us, fi, fr-be, hr, it, lv, nl-be, pt, sl, tr. Not usable if display type is SPICE. start Boolean, set 1 to start the vm VMware: --volume: name storage_pod Storage Pod ID from VMware datastore Datastore ID from VMware mode persistent/independent_persistent/independent_nonpersistent size_gb Integer number, volume size in GB thin true/false eager_zero true/false controller_key Associated SCSI controller key --interface: compute_type Type of the network adapter, for example one of: VirtualVmxnet3 VirtualE1000 See documentation center for your version of vSphere to find more details about available adapter types: https://www.vmware.com/support/pubs/ compute_network Network ID or Network Name from VMware --compute-attributes: \u001b[1mcluster\u001b[0m Cluster ID from VMware \u001b[1mcorespersocket\u001b[0m Number of cores per socket (applicable to hardware versions < 10 only) \u001b[1mcpus\u001b[0m CPU count \u001b[1mmemory_mb\u001b[0m Integer number, amount of memory in MB \u001b[1mpath\u001b[0m Path to folder \u001b[1mresource_pool\u001b[0m Resource Pool ID from VMware firmware automatic/bios/efi guest_id Guest OS ID form VMware hardware_version Hardware version ID from VMware memoryHotAddEnabled Must be a 1 or 0, lets you add memory resources while the machine is on cpuHotAddEnabled Must be a 1 or 0, lets you add CPU resources while the machine is on add_cdrom Must be a 1 or 0, Add a CD-ROM drive to the virtual machine annotation Annotation Notes scsi_controllers List with SCSI controllers definitions type - ID of the controller from VMware key - Key of the controller (e.g. 1000) boot_order Device names to specify the boot order start Must be a 1 or 0, whether to start the machine or not AzureRM: --volume: disk_size_gb Volume Size in GB (integer value) data_disk_caching Data Disk Caching (None, ReadOnly, ReadWrite) --interface: compute_network Select one of available Azure Subnets, must be an ID compute_public_ip Public IP (None, Static, Dynamic) compute_private_ip Static Private IP (expressed as true or false) --compute-attributes: resource_group Existing Azure Resource Group of user vm_size VM Size, eg. Standard_A0 etc. username The Admin username password The Admin password platform OS type eg. Linux ssh_key_data SSH key for passwordless authentication os_disk_caching OS disk caching premium_os_disk Premium OS Disk, Boolean as 0 or 1 script_command Custom Script Command script_uris Comma seperated file URIs GCE: --volume: size_gb Volume size in GB, integer value --interface: --compute-attributes: machine_type network associate_external_ip", "name": "help", "shortname": "h", "value": null @@ -21575,77 +21811,11 @@ ], "subcommands": [ { - "description": "Schedule errata for installation using katello-agent. NOTE: Katello-agent is deprecated and will be removed in a future release. Consider using remote execution instead.", + "description": "Not supported. Use the remote execution equivalent `hammer job-invocation create --feature katello_errata_install`.", "name": "apply", "options": [ { - "help": "Do not wait for the task", - "name": "async", - "shortname": null, - "value": null - }, - { - "help": "List of Errata ids to install. Will be removed in a future release", - "name": "errata-ids", - "shortname": null, - "value": "LIST" - }, - { - "help": "Whether or not to show all results", - "name": "full-result", - "shortname": null, - "value": "BOOLEAN" - }, - { - "help": "VALUE/NUMBER Host name/id", - "name": "host", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Host name/id", - "name": "host-id", - "shortname": null, - "value": null - }, - { - "help": "Sort field and order, eg. 'id DESC'", - "name": "order", - "shortname": null, - "value": "VALUE" - }, - { - "help": "Page number, starting at 1", - "name": "page", - "shortname": null, - "value": "NUMBER" - }, - { - "help": "Number of results per page to return", - "name": "per-page", - "shortname": null, - "value": "NUMBER" - }, - { - "help": "Search string for erratum to perform an action on", - "name": "search", - "shortname": null, - "value": "VALUE" - }, - { - "help": "Field to sort the results on", - "name": "sort-by", - "shortname": null, - "value": "VALUE" - }, - { - "help": "How to order the sorted results (e.g. ASC for ascending)", - "name": "sort-order", - "shortname": null, - "value": "VALUE" - }, - { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help", "name": "help", "shortname": "h", "value": null @@ -22012,7 +22182,7 @@ "value": "BOOLEAN" }, { - "help": "Print help --------------------------------------------------------|-----|---------|----- | ALL | DEFAULT | THIN --------------------------------------------------------|-----|---------|----- | x | x | x | x | x | | x | x | x | x | x | | x | x | group | x | x | resource | x | x | profile | x | x | name | x | x | | x | x | | x | x | at | x | x | report | x | x | (seconds) | x | x | Status/global status | x | x | Status/build status | x | x | Network/ipv4 address | x | x | Network/ipv6 address | x | x | Network/mac | x | x | Network/subnet ipv4 | x | x | Network/subnet ipv6 | x | x | Network/domain | x | x | Network/service provider/sp name | x | x | Network/service provider/sp ip | x | x | Network/service provider/sp mac | x | x | Network/service provider/sp subnet | x | x | interfaces/id | x | x | interfaces/identifier | x | x | interfaces/type | x | x | interfaces/mac address | x | x | interfaces/ipv4 address | x | x | interfaces/ipv6 address | x | x | interfaces/fqdn | x | x | system/architecture | x | x | system/operating system | x | x | system/build | x | x | system/medium | x | x | system/partition table | x | x | system/pxe loader | x | x | system/custom partition table | x | x | system/image | x | x | system/image file | x | x | system/use image | x | x | Parameters/ | x | x | parameters/ | x | x | info/owner | x | x | info/owner id | x | x | info/owner type | x | x | info/enabled | x | x | info/model | x | x | info/comment | x | x | proxy | x | x | information/content view/id | x | x | information/content view/name | x | x | information/lifecycle environment/id | x | x | information/lifecycle environment/name | x | x | information/content source/id | x | x | information/content source/name | x | x | information/kickstart repository/id | x | x | information/kickstart repository/name | x | x | information/applicable packages | x | x | information/upgradable packages | x | x | information/applicable errata/enhancement | x | x | information/applicable errata/bug fix | x | x | information/applicable errata/security | x | x | information/uuid | x | x | information/last checkin | x | x | information/release version | x | x | information/autoheal | x | x | information/registered to | x | x | information/registered at | x | x | information/registered by activation keys/ | x | x | information/system purpose/service level | x | x | information/system purpose/purpose usage | x | x | information/system purpose/purpose role | x | x | information/system purpose/purpose addons | x | x | status | x | x | collections/id | x | x | collections/name | x | x | --------------------------------------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help -------------------------------------------------------------------------|-----|---------|----- | ALL | DEFAULT | THIN -------------------------------------------------------------------------|-----|---------|----- | x | x | x | x | x | | x | x | x | x | x | | x | x | group | x | x | resource | x | x | profile | x | x | name | x | x | | x | x | | x | x | at | x | x | report | x | x | (seconds) | x | x | Status/global status | x | x | Status/build status | x | x | Network/ipv4 address | x | x | Network/ipv6 address | x | x | Network/mac | x | x | Network/subnet ipv4 | x | x | Network/subnet ipv6 | x | x | Network/domain | x | x | Network/service provider/sp name | x | x | Network/service provider/sp ip | x | x | Network/service provider/sp mac | x | x | Network/service provider/sp subnet | x | x | interfaces/id | x | x | interfaces/identifier | x | x | interfaces/type | x | x | interfaces/mac address | x | x | interfaces/ipv4 address | x | x | interfaces/ipv6 address | x | x | interfaces/fqdn | x | x | system/architecture | x | x | system/operating system | x | x | system/build | x | x | system/medium | x | x | system/partition table | x | x | system/pxe loader | x | x | system/custom partition table | x | x | system/image | x | x | system/image file | x | x | system/use image | x | x | Parameters/ | x | x | parameters/ | x | x | info/owner | x | x | info/owner id | x | x | info/owner type | x | x | info/enabled | x | x | info/model | x | x | info/comment | x | x | proxy | x | x | information/content view environments/content view/id | x | x | information/content view environments/content view/name | x | x | information/content view environments/content view/composite | x | x | information/content view environments/lifecycle environment/id | x | x | information/content view environments/lifecycle environment/name | x | x | information/content source/id | x | x | information/content source/name | x | x | information/kickstart repository/id | x | x | information/kickstart repository/name | x | x | information/applicable packages | x | x | information/upgradable packages | x | x | information/applicable errata/enhancement | x | x | information/applicable errata/bug fix | x | x | information/applicable errata/security | x | x | information/uuid | x | x | information/last checkin | x | x | information/release version | x | x | information/autoheal | x | x | information/registered to | x | x | information/registered at | x | x | information/registered by activation keys/ | x | x | information/system purpose/service level | x | x | information/system purpose/purpose usage | x | x | information/system purpose/purpose role | x | x | information/system purpose/purpose addons | x | x | status | x | x | collections/id | x | x | collections/name | x | x | -------------------------------------------------------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -22812,7 +22982,7 @@ "value": "BOOLEAN" }, { - "help": "Print help -----------------------|-----|---------|----- | ALL | DEFAULT | THIN -----------------------|-----|---------|----- | x | x | x | x | x | x system | x | x | group | x | x | | x | x | | x | x | status | x | x | | x | | | x | | information | x | | view | x | x | environment | x | x | | x | | | x | | | x | | status | x | x | -----------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string string string string Values: mismatched, matched, not_specified string string string date string string boolean boot_time Values: true, false Values: built, pending, token_expired, build_failed text string integer string string integer datetime integer string integer Values: security_needed, errata_needed, updated, unknown Values: ok, error string Values: ok, warning, error string string string string string integer string string boolean string integer string infrastructure_facet.foreman infrastructure_facet.smart_proxy_id Values: reporting, no_report Values: disconnect, sync integer string datetime string string job_invocation.id string job_invocation.result Values: cancelled, failed, pending, success datetime datetime string integer string integer string Values: true, false string string string integer string string string integer string string string string integer string string string string string integer string Values: mismatched, matched, not_specified string integer datetime string string reported.boot_time reported.cores reported.disks_total reported.kernel_version reported.ram reported.sockets reported.virtual Values: true, false string string text Values: mismatched, matched, not_specified string Values: mismatched, matched, not_specified string status.applied integer status.enabled Values: true, false status.failed integer status.failed_restarts integer status.interesting Values: true, false status.pending integer status.restarted integer status.skipped integer string subnet.name text string subnet6.name text string string Values: valid, partial, invalid, unknown, disabled, unsubscribed_hypervisor string Values: reboot_needed, process_restart_needed, updated string string text Values: mismatched, matched, not_specified user.firstname string user.lastname string user.login string user.mail string string usergroup.name string string", + "help": "Print help -----------------------|-----|---------|----- | ALL | DEFAULT | THIN -----------------------|-----|---------|----- | x | x | x | x | x | x system | x | x | group | x | x | | x | x | | x | x | status | x | x | | x | | | x | | information | x | | view | x | x | environment | x | x | | x | | | x | | | x | | status | x | x | -----------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string string string string Values: mismatched, matched, not_specified string string date string string boolean boot_time Values: true, false Values: built, pending, token_expired, build_failed text string integer configuration_status.applied integer configuration_status.enabled Values: true, false configuration_status.failed integer configuration_status.failed_restarts integer configuration_status.interesting Values: true, false configuration_status.pending integer configuration_status.restarted integer configuration_status.skipped integer string string datetime integer string integer Values: security_needed, errata_needed, updated, unknown Values: ok, error string Values: ok, warning, error string string string string string integer string string boolean string integer string infrastructure_facet.foreman Values: true, false infrastructure_facet.smart_proxy_id Values: reporting, no_report Values: disconnect, sync integer string datetime string string job_invocation.id string job_invocation.result Values: cancelled, failed, pending, success datetime datetime string string integer string Values: true, false string string string integer string string string integer string string string string integer string string string string string integer string Values: mismatched, matched, not_specified Values: PXELinux_BIOS, PXELinux_UEFI, Grub_UEFI, Grub2_BIOS, Grub2_ELF, Grub2_UEFI, Grub2_UEFI_SecureBoot, Grub2_UEFI_HTTP, Grub2_UEFI_HTTPS, Grub2_UEFI_HTTPS_SecureBoot, iPXE_Embedded, iPXE_UEFI_HTTP, iPXE_Chain_BIOS, iPXE_Chain_UEFI string integer datetime string string reported.bios_release_date reported.bios_vendor reported.bios_version reported.boot_time reported.cores reported.disks_total reported.kernel_version reported.ram reported.sockets reported.virtual Values: true, false string string Values: full_support, maintenance_support, approaching_end_of_maintenance, extended_support, approaching_end_of_support, support_ended text Values: mismatched, matched, not_specified string Values: mismatched, matched, not_specified string status.applied integer status.enabled Values: true, false status.failed integer status.failed_restarts integer status.interesting Values: true, false status.pending integer status.restarted integer status.skipped integer string subnet.name text string subnet6.name text string string Values: valid, partial, invalid, unknown, disabled, unsubscribed_hypervisor string Values: reboot_needed, process_restart_needed, updated string string text Values: mismatched, matched, not_specified user.firstname string user.lastname string user.login string user.mail string string usergroup.name string string", "name": "help", "shortname": "h", "value": null @@ -22833,35 +23003,11 @@ ], "subcommands": [ { - "description": "Install packages remotely using katello-agent. NOTE: Katello-agent is deprecated and will be removed in a future release. Consider using remote execution instead.", + "description": "Not supported. Use the remote execution equivalent `hammer job-invocation create --feature katello_package_install`.", "name": "install", "options": [ { - "help": "Do not wait for the task", - "name": "async", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/id of the host", - "name": "host", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/id of the host", - "name": "host-id", - "shortname": null, - "value": null - }, - { - "help": "List of package names", - "name": "packages", - "shortname": null, - "value": "LIST" - }, - { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help", "name": "help", "shortname": "h", "value": null @@ -22943,35 +23089,11 @@ "subcommands": [] }, { - "description": "Uninstall packages remotely using katello-agent. NOTE: Katello-agent is deprecated and will be removed in a future release. Consider using remote execution instead.", + "description": "Not supported. Use the remote execution equivalent `hammer job-invocation create --feature katello_package_remove`.", "name": "remove", "options": [ { - "help": "Do not wait for the task", - "name": "async", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/id of the host", - "name": "host", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/id of the host", - "name": "host-id", - "shortname": null, - "value": null - }, - { - "help": "List of package names", - "name": "packages", - "shortname": null, - "value": "LIST" - }, - { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help", "name": "help", "shortname": "h", "value": null @@ -22980,35 +23102,11 @@ "subcommands": [] }, { - "description": "Update packages remotely using katello-agent. NOTE: Katello-agent is deprecated and will be removed in a future release. Consider using remote execution instead.", + "description": "Not supported. Use the remote execution equivalent `hammer job-invocation create --feature katello_package_update`.", "name": "upgrade", "options": [ { - "help": "Do not wait for the task", - "name": "async", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/id of the host", - "name": "host", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/id of the host", - "name": "host-id", - "shortname": null, - "value": null - }, - { - "help": "List of packages names", - "name": "packages", - "shortname": null, - "value": "LIST" - }, - { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help", "name": "help", "shortname": "h", "value": null @@ -23017,29 +23115,11 @@ "subcommands": [] }, { - "description": "Update packages remotely using katello-agent. NOTE: Katello-agent is deprecated and will be removed in a future release. Consider using remote execution instead.", + "description": "Not supported. Use the remote execution equivalent `hammer job-invocation create --feature katello_package_update`.", "name": "upgrade-all", "options": [ { - "help": "Do not wait for the task", - "name": "async", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/id of the host", - "name": "host", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/id of the host", - "name": "host-id", - "shortname": null, - "value": null - }, - { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help", "name": "help", "shortname": "h", "value": null @@ -23050,7 +23130,7 @@ ] }, { - "description": "Manage package-groups on your hosts", + "description": "Manage package-groups on your hosts. These commands are no longer available Use the remote execution equivalent", "name": "package-group", "options": [ { @@ -23062,35 +23142,11 @@ ], "subcommands": [ { - "description": "Install packages remotely using katello-agent. NOTE: Katello-agent is deprecated and will be removed in a future release. Consider using remote execution instead.", + "description": "Not supported. Use the remote execution equivalent `hammer job-invocation create --feature katello_group_install`.", "name": "install", "options": [ { - "help": "Do not wait for the task", - "name": "async", - "shortname": null, - "value": null - }, - { - "help": "List of package group names (Deprecated)", - "name": "groups", - "shortname": null, - "value": "LIST" - }, - { - "help": "VALUE/NUMBER Name/id of the host", - "name": "host", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/id of the host", - "name": "host-id", - "shortname": null, - "value": null - }, - { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help", "name": "help", "shortname": "h", "value": null @@ -23099,35 +23155,11 @@ "subcommands": [] }, { - "description": "Uninstall packages remotely using katello-agent. NOTE: Katello-agent is deprecated and will be removed in a future release. Consider using remote execution instead.", + "description": "Not supported. Use the remote execution equivalent `hammer job-invocation create --feature katello_group_remove`.", "name": "remove", "options": [ { - "help": "Do not wait for the task", - "name": "async", - "shortname": null, - "value": null - }, - { - "help": "List of package group names (Deprecated)", - "name": "groups", - "shortname": null, - "value": "LIST" - }, - { - "help": "VALUE/NUMBER Name/id of the host", - "name": "host", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/id of the host", - "name": "host-id", - "shortname": null, - "value": null - }, - { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help", "name": "help", "shortname": "h", "value": null @@ -24749,7 +24781,7 @@ "value": "KEY_VALUE_LIST" }, { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string parameters accept format defined by its schema (bold are required; <> contains acceptable type; [] contains acceptable value): \"name=\\,value=\\,parameter_type=[string|boolean|integer|real|array|hash|yaml|json]\\,hidden_value=[true|false|1|0], ... \" \"product_id=\\,product_name=\\,arch=\\,version=, ... \" mac ip Possible values: interface, bmc, bond, bridge name subnet_id domain_id identifier true/false true/false, each managed hosts needs to have one primary interface. true/false true/false virtual=true: tag VLAN tag, this attribute has precedence over the subnet VLAN ID. Only for virtual interfaces. attached_to Identifier of the interface to which this interface belongs, e.g. eth1. type=bond: mode Possible values: balance-rr, active-backup, balance-xor, broadcast, 802.3ad, balance-tlb, balance-alb attached_devices Identifiers of slave interfaces, e.g. [eth1,eth2] bond_options type=bmc: provider always IPMI username password \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --volume: --interface: --compute-attributes: availability_zone flavor_id groups security_group_ids managed_ip Libvirt: --volume: \u001b[1mpool_name\u001b[0m One of available storage pools \u001b[1mcapacity\u001b[0m String value, e.g. 10G allocation Initial allocation, e.g. 0G format_type Possible values: raw, qcow2 --interface: compute_type Possible values: bridge, network compute_bridge Name of interface according to type compute_model Possible values: virtio, rtl8139, ne2k_pci, pcnet, e1000 compute_network Libvirt instance network, e.g. default --compute-attributes: \u001b[1mcpus\u001b[0m Number of CPUs \u001b[1mmemory\u001b[0m String, amount of memory, value in bytes cpu_mode Possible values: default, host-model, host-passthrough boot_order Device names to specify the boot order start Boolean (expressed as 0 or 1), whether to start the machine or not OpenStack: --volume: --interface: --compute-attributes: availability_zone boot_from_volume flavor_ref image_ref tenant_id security_groups network oVirt: --volume: size_gb Volume size in GB, integer value storage_domain ID or name of storage domain bootable Boolean, set 1 for bootable, only one volume can be bootable preallocate Boolean, set 1 to preallocate wipe_after_delete Boolean, set 1 to wipe disk after delete interface Disk interface name, must be ide, virto or virto_scsi --interface: compute_name Compute name, e.g. eth0 compute_network Select one of available networks for a cluster, must be an ID or a name compute_interface Interface type compute_vnic_profile Vnic Profile --compute-attributes: cluster ID or name of cluster to use template Hardware profile to use cores Integer value, number of cores sockets Integer value, number of sockets memory Amount of memory, integer value in bytes ha Boolean, set 1 to high availability display_type Possible values: VNC, SPICE keyboard_layout Possible values: ar, de-ch, es, fo, fr-ca, hu, ja, mk, no, pt-br, sv, da, en-gb, et, fr, fr-ch, is, lt, nl, pl, ru, th, de, en-us, fi, fr-be, hr, it, lv, nl-be, pt, sl, tr. Not usable if display type is SPICE. start Boolean, set 1 to start the vm Rackspace: --volume: --interface: --compute-attributes: flavor_id VMware: --volume: name storage_pod Storage Pod ID from VMware datastore Datastore ID from VMware mode persistent/independent_persistent/independent_nonpersistent size_gb Integer number, volume size in GB thin true/false eager_zero true/false controller_key Associated SCSI controller key --interface: compute_type Type of the network adapter, for example one of: VirtualVmxnet3 VirtualE1000 See documentation center for your version of vSphere to find more details about available adapter types: https://www.vmware.com/support/pubs/ compute_network Network ID or Network Name from VMware --compute-attributes: \u001b[1mcluster\u001b[0m Cluster ID from VMware \u001b[1mcorespersocket\u001b[0m Number of cores per socket (applicable to hardware versions < 10 only) \u001b[1mcpus\u001b[0m CPU count \u001b[1mmemory_mb\u001b[0m Integer number, amount of memory in MB \u001b[1mpath\u001b[0m Path to folder \u001b[1mresource_pool\u001b[0m Resource Pool ID from VMware firmware automatic/bios/efi guest_id Guest OS ID form VMware hardware_version Hardware version ID from VMware memoryHotAddEnabled Must be a 1 or 0, lets you add memory resources while the machine is on cpuHotAddEnabled Must be a 1 or 0, lets you add CPU resources while the machine is on add_cdrom Must be a 1 or 0, Add a CD-ROM drive to the virtual machine annotation Annotation Notes scsi_controllers List with SCSI controllers definitions type - ID of the controller from VMware key - Key of the controller (e.g. 1000) boot_order Device names to specify the boot order start Must be a 1 or 0, whether to start the machine or not AzureRM: --volume: disk_size_gb Volume Size in GB (integer value) data_disk_caching Data Disk Caching (None, ReadOnly, ReadWrite) --interface: compute_network Select one of available Azure Subnets, must be an ID compute_public_ip Public IP (None, Static, Dynamic) compute_private_ip Static Private IP (expressed as true or false) --compute-attributes: resource_group Existing Azure Resource Group of user vm_size VM Size, eg. Standard_A0 etc. username The Admin username password The Admin password platform OS type eg. Linux ssh_key_data SSH key for passwordless authentication os_disk_caching OS disk caching premium_os_disk Premium OS Disk, Boolean as 0 or 1 script_command Custom Script Command script_uris Comma seperated file URIs GCE: --volume: size_gb Volume size in GB, integer value --interface: --compute-attributes: machine_type network associate_external_ip", + "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string parameters accept format defined by its schema (bold are required; <> contains acceptable type; [] contains acceptable value): \"name=\\,value=\\,parameter_type=[string|boolean|integer|real|array|hash|yaml|json]\\,hidden_value=[true|false|1|0], ... \" \"product_id=\\,product_name=\\,arch=\\,version=, ... \" mac ip Possible values: interface, bmc, bond, bridge name subnet_id domain_id identifier true/false true/false, each managed hosts needs to have one primary interface. true/false true/false virtual=true: tag VLAN tag, this attribute has precedence over the subnet VLAN ID. Only for virtual interfaces. attached_to Identifier of the interface to which this interface belongs, e.g. eth1. type=bond: mode Possible values: balance-rr, active-backup, balance-xor, broadcast, 802.3ad, balance-tlb, balance-alb attached_devices Identifiers of slave interfaces, e.g. [eth1,eth2] bond_options type=bmc: provider always IPMI username password \u001b[1mNOTE:\u001b[0m Bold attributes are required. EC2: --volume: --interface: --compute-attributes: availability_zone flavor_id groups security_group_ids managed_ip Libvirt: --volume: \u001b[1mpool_name\u001b[0m One of available storage pools \u001b[1mcapacity\u001b[0m String value, e.g. 10G allocation Initial allocation, e.g. 0G format_type Possible values: raw, qcow2 --interface: compute_type Possible values: bridge, network compute_bridge Name of interface according to type compute_model Possible values: virtio, rtl8139, ne2k_pci, pcnet, e1000 compute_network Libvirt instance network, e.g. default --compute-attributes: \u001b[1mcpus\u001b[0m Number of CPUs \u001b[1mmemory\u001b[0m String, amount of memory, value in bytes cpu_mode Possible values: default, host-model, host-passthrough boot_order Device names to specify the boot order start Boolean (expressed as 0 or 1), whether to start the machine or not OpenStack: --volume: --interface: --compute-attributes: availability_zone boot_from_volume flavor_ref image_ref tenant_id security_groups network oVirt: --volume: size_gb Volume size in GB, integer value storage_domain ID or name of storage domain bootable Boolean, set 1 for bootable, only one volume can be bootable preallocate Boolean, set 1 to preallocate wipe_after_delete Boolean, set 1 to wipe disk after delete interface Disk interface name, must be ide, virtio or virtio_scsi --interface: compute_name Compute name, e.g. eth0 compute_network Select one of available networks for a cluster, must be an ID or a name compute_interface Interface type compute_vnic_profile Vnic Profile --compute-attributes: cluster ID or name of cluster to use template Hardware profile to use cores Integer value, number of cores sockets Integer value, number of sockets memory Amount of memory, integer value in bytes ha Boolean, set 1 to high availability display_type Possible values: VNC, SPICE keyboard_layout Possible values: ar, de-ch, es, fo, fr-ca, hu, ja, mk, no, pt-br, sv, da, en-gb, et, fr, fr-ch, is, lt, nl, pl, ru, th, de, en-us, fi, fr-be, hr, it, lv, nl-be, pt, sl, tr. Not usable if display type is SPICE. start Boolean, set 1 to start the vm VMware: --volume: name storage_pod Storage Pod ID from VMware datastore Datastore ID from VMware mode persistent/independent_persistent/independent_nonpersistent size_gb Integer number, volume size in GB thin true/false eager_zero true/false controller_key Associated SCSI controller key --interface: compute_type Type of the network adapter, for example one of: VirtualVmxnet3 VirtualE1000 See documentation center for your version of vSphere to find more details about available adapter types: https://www.vmware.com/support/pubs/ compute_network Network ID or Network Name from VMware --compute-attributes: \u001b[1mcluster\u001b[0m Cluster ID from VMware \u001b[1mcorespersocket\u001b[0m Number of cores per socket (applicable to hardware versions < 10 only) \u001b[1mcpus\u001b[0m CPU count \u001b[1mmemory_mb\u001b[0m Integer number, amount of memory in MB \u001b[1mpath\u001b[0m Path to folder \u001b[1mresource_pool\u001b[0m Resource Pool ID from VMware firmware automatic/bios/efi guest_id Guest OS ID form VMware hardware_version Hardware version ID from VMware memoryHotAddEnabled Must be a 1 or 0, lets you add memory resources while the machine is on cpuHotAddEnabled Must be a 1 or 0, lets you add CPU resources while the machine is on add_cdrom Must be a 1 or 0, Add a CD-ROM drive to the virtual machine annotation Annotation Notes scsi_controllers List with SCSI controllers definitions type - ID of the controller from VMware key - Key of the controller (e.g. 1000) boot_order Device names to specify the boot order start Must be a 1 or 0, whether to start the machine or not AzureRM: --volume: disk_size_gb Volume Size in GB (integer value) data_disk_caching Data Disk Caching (None, ReadOnly, ReadWrite) --interface: compute_network Select one of available Azure Subnets, must be an ID compute_public_ip Public IP (None, Static, Dynamic) compute_private_ip Static Private IP (expressed as true or false) --compute-attributes: resource_group Existing Azure Resource Group of user vm_size VM Size, eg. Standard_A0 etc. username The Admin username password The Admin password platform OS type eg. Linux ssh_key_data SSH key for passwordless authentication os_disk_caching OS disk caching premium_os_disk Premium OS Disk, Boolean as 0 or 1 script_command Custom Script Command script_uris Comma seperated file URIs GCE: --volume: size_gb Volume size in GB, integer value --interface: --compute-attributes: machine_type network associate_external_ip", "name": "help", "shortname": "h", "value": null @@ -25010,7 +25042,7 @@ "subcommands": [] }, { - "description": "Manipulate errata for a host collection", + "description": "Manage errata on your host collections. These commands are no longer available. Use the remote execution equivalent", "name": "erratum", "options": [ { @@ -25022,53 +25054,11 @@ ], "subcommands": [ { - "description": "Install errata on content hosts contained within a host collection", + "description": "Not supported. Use the remote execution equivalent `hammer job-invocation create --feature katello_errata_install`. Specify the host collection with the --search-query parameter, e.g. `--search-query \"host_collection = MyCollection\"` or `--search-query \"host_collection_id=6\"`", "name": "install", "options": [ { - "help": "List of Errata to install", - "name": "errata", - "shortname": null, - "value": "LIST" - }, - { - "help": "Host Collection ID", - "name": "id", - "shortname": null, - "value": "VALUE" - }, - { - "help": "Host Collection Name", - "name": "name", - "shortname": null, - "value": "VALUE" - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-id", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-title", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-label", - "shortname": null, - "value": null - }, - { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help", "name": "help", "shortname": "h", "value": null @@ -25197,7 +25187,7 @@ "value": "BOOLEAN" }, { - "help": "Print help ------------|-----|---------|----- | ALL | DEFAULT | THIN ------------|-----|---------|----- | x | x | x | x | x | x | x | | | x | | | x | | ------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string string string string Values: mismatched, matched, not_specified string string string date string string boolean boot_time Values: true, false Values: built, pending, token_expired, build_failed text string integer string string integer datetime integer string integer Values: security_needed, errata_needed, updated, unknown Values: ok, error string Values: ok, warning, error string string string string string integer string string boolean string integer string infrastructure_facet.foreman infrastructure_facet.smart_proxy_id Values: reporting, no_report Values: disconnect, sync integer string datetime string string job_invocation.id string job_invocation.result Values: cancelled, failed, pending, success datetime datetime string integer string integer string Values: true, false string string string integer string string string integer string string string string integer string string string string string integer string Values: mismatched, matched, not_specified string integer datetime string string reported.boot_time reported.cores reported.disks_total reported.kernel_version reported.ram reported.sockets reported.virtual Values: true, false string string text Values: mismatched, matched, not_specified string Values: mismatched, matched, not_specified string status.applied integer status.enabled Values: true, false status.failed integer status.failed_restarts integer status.interesting Values: true, false status.pending integer status.restarted integer status.skipped integer string subnet.name text string subnet6.name text string string Values: valid, partial, invalid, unknown, disabled, unsubscribed_hypervisor string Values: reboot_needed, process_restart_needed, updated string string text Values: mismatched, matched, not_specified user.firstname string user.lastname string user.login string user.mail string string usergroup.name string string", + "help": "Print help ------------|-----|---------|----- | ALL | DEFAULT | THIN ------------|-----|---------|----- | x | x | x | x | x | x | x | | | x | | | x | | ------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string string string string Values: mismatched, matched, not_specified string string date string string boolean boot_time Values: true, false Values: built, pending, token_expired, build_failed text string integer configuration_status.applied integer configuration_status.enabled Values: true, false configuration_status.failed integer configuration_status.failed_restarts integer configuration_status.interesting Values: true, false configuration_status.pending integer configuration_status.restarted integer configuration_status.skipped integer string string datetime integer string integer Values: security_needed, errata_needed, updated, unknown Values: ok, error string Values: ok, warning, error string string string string string integer string string boolean string integer string infrastructure_facet.foreman Values: true, false infrastructure_facet.smart_proxy_id Values: reporting, no_report Values: disconnect, sync integer string datetime string string job_invocation.id string job_invocation.result Values: cancelled, failed, pending, success datetime datetime string string integer string Values: true, false string string string integer string string string integer string string string string integer string string string string string integer string Values: mismatched, matched, not_specified Values: PXELinux_BIOS, PXELinux_UEFI, Grub_UEFI, Grub2_BIOS, Grub2_ELF, Grub2_UEFI, Grub2_UEFI_SecureBoot, Grub2_UEFI_HTTP, Grub2_UEFI_HTTPS, Grub2_UEFI_HTTPS_SecureBoot, iPXE_Embedded, iPXE_UEFI_HTTP, iPXE_Chain_BIOS, iPXE_Chain_UEFI string integer datetime string string reported.bios_release_date reported.bios_vendor reported.bios_version reported.boot_time reported.cores reported.disks_total reported.kernel_version reported.ram reported.sockets reported.virtual Values: true, false string string Values: full_support, maintenance_support, approaching_end_of_maintenance, extended_support, approaching_end_of_support, support_ended text Values: mismatched, matched, not_specified string Values: mismatched, matched, not_specified string status.applied integer status.enabled Values: true, false status.failed integer status.failed_restarts integer status.interesting Values: true, false status.pending integer status.restarted integer status.skipped integer string subnet.name text string subnet6.name text string string Values: valid, partial, invalid, unknown, disabled, unsubscribed_hypervisor string Values: reboot_needed, process_restart_needed, updated string string text Values: mismatched, matched, not_specified user.firstname string user.lastname string user.login string user.mail string string usergroup.name string string", "name": "help", "shortname": "h", "value": null @@ -25370,7 +25360,7 @@ "subcommands": [] }, { - "description": "Manipulate packages for a host collection", + "description": "Manage packages on your host collections. These commands are no longer available. Use the remote execution equivalent", "name": "package", "options": [ { @@ -25382,53 +25372,11 @@ ], "subcommands": [ { - "description": "Install packages on content hosts contained within a host collection", + "description": "Not supported. Use the remote execution equivalent `hammer job-invocation create --feature katello_package_install`. Specify the host collection with the --search-query parameter, e.g. `--search-query \"host_collection = MyCollection\"` or `--search-query \"host_collection_id=6\"`", "name": "install", "options": [ { - "help": "Host Collection ID", - "name": "id", - "shortname": null, - "value": "VALUE" - }, - { - "help": "Host Collection Name", - "name": "name", - "shortname": null, - "value": "VALUE" - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-id", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-title", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-label", - "shortname": null, - "value": null - }, - { - "help": "Comma-separated list of packages to install", - "name": "packages", - "shortname": null, - "value": "LIST" - }, - { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help", "name": "help", "shortname": "h", "value": null @@ -25437,53 +25385,11 @@ "subcommands": [] }, { - "description": "Remove packages on content hosts contained within a host collection", + "description": "Not supported. Use the remote execution equivalent `hammer job-invocation create --feature katello_package_remove`. Specify the host collection with the --search-query parameter, e.g. `--search-query \"host_collection = MyCollection\"` or `--search-query \"host_collection_id=6\"`", "name": "remove", "options": [ { - "help": "Host Collection ID", - "name": "id", - "shortname": null, - "value": "VALUE" - }, - { - "help": "Host Collection Name", - "name": "name", - "shortname": null, - "value": "VALUE" - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-id", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-title", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-label", - "shortname": null, - "value": null - }, - { - "help": "Comma-separated list of packages to install", - "name": "packages", - "shortname": null, - "value": "LIST" - }, - { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help", "name": "help", "shortname": "h", "value": null @@ -25492,53 +25398,11 @@ "subcommands": [] }, { - "description": "Update packages on content hosts contained within a host collection", + "description": "Not supported. Use the remote execution equivalent `hammer job-invocation create --feature katello_package_update`. Specify the host collection with the --search-query parameter, e.g. `--search-query \"host_collection = MyCollection\"` or `--search-query \"host_collection_id=6\"`", "name": "update", "options": [ { - "help": "Host Collection ID", - "name": "id", - "shortname": null, - "value": "VALUE" - }, - { - "help": "Host Collection Name", - "name": "name", - "shortname": null, - "value": "VALUE" - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-id", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-title", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-label", - "shortname": null, - "value": null - }, - { - "help": "Comma-separated list of packages to install", - "name": "packages", - "shortname": null, - "value": "LIST" - }, - { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help", "name": "help", "shortname": "h", "value": null @@ -25549,7 +25413,7 @@ ] }, { - "description": "Manipulate package-groups for a host collection", + "description": "Manage package-groups on your host collections. These commands are no longer available. Use the remote execution equivalent", "name": "package-group", "options": [ { @@ -25561,53 +25425,11 @@ ], "subcommands": [ { - "description": "Install package-groups on content hosts contained within a host collection", + "description": "Not supported. Use the remote execution equivalent `hammer job-invocation create --feature katello_group_install`. Specify the host collection with the --search-query parameter, e.g. `--search-query \"host_collection = MyCollection\"` or `--search-query \"host_collection_id=6\"`", "name": "install", "options": [ { - "help": "Host Collection ID", - "name": "id", - "shortname": null, - "value": "VALUE" - }, - { - "help": "Host Collection Name", - "name": "name", - "shortname": null, - "value": "VALUE" - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-id", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-title", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-label", - "shortname": null, - "value": null - }, - { - "help": "Comma-separated list of package-groups to install", - "name": "package-groups", - "shortname": null, - "value": "LIST" - }, - { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help", "name": "help", "shortname": "h", "value": null @@ -25616,53 +25438,11 @@ "subcommands": [] }, { - "description": "Remove package-groups on content hosts contained within a host collection", + "description": "Not supported. Use the remote execution equivalent `hammer job-invocation create --feature katello_group_remove`. Specify the host collection with the --search-query parameter, e.g. `--search-query \"host_collection = MyCollection\"` or `--search-query \"host_collection_id=6\"`", "name": "remove", "options": [ { - "help": "Host Collection ID", - "name": "id", - "shortname": null, - "value": "VALUE" - }, - { - "help": "Host Collection Name", - "name": "name", - "shortname": null, - "value": "VALUE" - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-id", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-title", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-label", - "shortname": null, - "value": null - }, - { - "help": "Comma-separated list of package-groups to install", - "name": "package-groups", - "shortname": null, - "value": "LIST" - }, - { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help", "name": "help", "shortname": "h", "value": null @@ -25671,53 +25451,11 @@ "subcommands": [] }, { - "description": "Update package-groups on content hosts contained within a host collection", + "description": "Not supported. Use the remote execution equivalent `hammer job-invocation create --feature katello_group_update`. Specify the host collection with the --search-query parameter, e.g. `--search-query \"host_collection = MyCollection\"` or `--search-query \"host_collection_id=6\"`", "name": "update", "options": [ { - "help": "Host Collection ID", - "name": "id", - "shortname": null, - "value": "VALUE" - }, - { - "help": "Host Collection Name", - "name": "name", - "shortname": null, - "value": "VALUE" - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-id", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-title", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Name/Title/Label/Id of associated organization", - "name": "organization-label", - "shortname": null, - "value": null - }, - { - "help": "Comma-separated list of package-groups to install", - "name": "package-groups", - "shortname": null, - "value": "LIST" - }, - { - "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help", "name": "help", "shortname": "h", "value": null @@ -25910,19 +25648,19 @@ "value": "BOOLEAN" }, { - "help": "VALUE/NUMBER Name/title/id of the Host group to register the host in", + "help": "VALUE/NUMBER Name/title/id of the Host group to register the host in", "name": "hostgroup", "shortname": null, "value": null }, { - "help": "VALUE/NUMBER Name/title/id of the Host group to register the host in", + "help": "VALUE/NUMBER Name/title/id of the Host group to register the host in", "name": "hostgroup-id", "shortname": null, "value": null }, { - "help": "VALUE/NUMBER Name/title/id of the Host group to register the host in", + "help": "VALUE/NUMBER Name/title/id of the Host group to register the host in", "name": "hostgroup-title", "shortname": null, "value": null @@ -25940,67 +25678,55 @@ "value": "BOOLEAN" }, { - "help": "Expiration of the authorization token (in hours)", + "help": "Expiration of the authorization token (in hours), 0 means 'unlimited'.", "name": "jwt-expiration", "shortname": null, "value": "NUMBER" }, { - "help": "VALUE/NUMBER Lifecycle environment for the host.", - "name": "lifecycle-environment", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Lifecycle environment for the host.", - "name": "lifecycle-environment-id", - "shortname": null, - "value": null - }, - { - "help": "VALUE/NUMBER Set the current location context for the request", + "help": "VALUE/NUMBER Set the current location context for the request", "name": "location", "shortname": null, "value": null }, { - "help": "VALUE/NUMBER Set the current location context for the request", + "help": "VALUE/NUMBER Set the current location context for the request", "name": "location-id", "shortname": null, "value": null }, { - "help": "VALUE/NUMBER Set the current location context for the request", + "help": "VALUE/NUMBER Set the current location context for the request", "name": "location-title", "shortname": null, "value": null }, { - "help": "VALUE/NUMBER Title/id of the Operating System to register the host in. Operating system must have a `host_init_config` template assigned", + "help": "VALUE/NUMBER Title/id of the Operating System to register the host in. Operating system must have a `host_init_config` template assigned", "name": "operatingsystem", "shortname": null, "value": null }, { - "help": "VALUE/NUMBER Title/id of the Operating System to register the host in. Operating system must have a `host_init_config` template assigned", + "help": "VALUE/NUMBER Title/id of the Operating System to register the host in. Operating system must have a `host_init_config` template assigned", "name": "operatingsystem-id", "shortname": null, "value": null }, { - "help": "VALUE/NUMBER Set the current organization context for the request", + "help": "VALUE/NUMBER Set the current organization context for the request", "name": "organization", "shortname": null, "value": null }, { - "help": "VALUE/NUMBER Set the current organization context for the request", + "help": "VALUE/NUMBER Set the current organization context for the request", "name": "organization-id", "shortname": null, "value": null }, { - "help": "VALUE/NUMBER Set the current organization context for the request", + "help": "VALUE/NUMBER Set the current organization context for the request", "name": "organization-title", "shortname": null, "value": null @@ -26048,13 +25774,13 @@ "value": "BOOLEAN" }, { - "help": "VALUE/NUMBER Name/id of the Capsule. This Capsule must have enabled both the 'Templates' and 'Registration' features", + "help": "VALUE/NUMBER Name/id of the Capsule. This Capsule must have enabled both the 'Templates' and 'Registration' features", "name": "smart-proxy", "shortname": null, "value": null }, { - "help": "VALUE/NUMBER Name/id of the Capsule. This Capsule must have enabled both the 'Templates' and 'Registration' features", + "help": "VALUE/NUMBER Name/id of the Capsule. This Capsule must have enabled both the 'Templates' and 'Registration' features", "name": "smart-proxy-id", "shortname": null, "value": null @@ -28387,8 +28113,8 @@ "value": "ENUM" }, { - "help": "Distribute tasks over N seconds", - "name": "time-span", + "help": "Override the global time to pickup interval for this invocation only", + "name": "time-to-pickup", "shortname": null, "value": "NUMBER" }, @@ -28442,7 +28168,7 @@ "value": null }, { - "help": "Print help --------------------|-----|-------- | ALL | DEFAULT --------------------|-----|-------- | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x ordering | x | x | x | x category | x | x | x | x line | x | x logic id | x | x | x | x --------------------|-----|-------- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help --------------------|-----|-------- | ALL | DEFAULT --------------------|-----|-------- | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x ordering | x | x | x | x category | x | x | x | x line | x | x logic id | x | x to pickup | x | x | x | x --------------------|-----|-------- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -28663,6 +28389,12 @@ "description": "Create a job template", "name": "create", "options": [ + { + "help": "Enable the callback plugin for this template", + "name": "ansible-callback-enabled", + "shortname": null, + "value": "BOOLEAN" + }, { "help": "", "name": "audit-comment", @@ -29131,7 +28863,7 @@ "value": null }, { - "help": "Print help ---------------|-----|---------|----- | ALL | DEFAULT | THIN ---------------|-----|---------|----- | x | x | x | x | x | x category | x | x | | x | x | | x | x | | x | x | | x | x | Locations/ | x | x | Organizations/ | x | x | ---------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help -------------------------|-----|---------|----- | ALL | DEFAULT | THIN -------------------------|-----|---------|----- | x | x | x | x | x | x category | x | x | | x | x | | x | x | callback enabled | x | x | | x | x | | x | x | Locations/ | x | x | Organizations/ | x | x | -------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -29222,6 +28954,12 @@ "description": "Update a job template", "name": "update", "options": [ + { + "help": "Enable the callback plugin for this template", + "name": "ansible-callback-enabled", + "shortname": null, + "value": "BOOLEAN" + }, { "help": "", "name": "audit-comment", @@ -29607,10 +29345,10 @@ "value": "VALUE" }, { - "help": "Set true if you want to see only library environments Possible value(s): 'true', 'false'", + "help": "Set true if you want to see only library environments", "name": "library", "shortname": null, - "value": "ENUM" + "value": "BOOLEAN" }, { "help": "Filter only environments containing this name", @@ -29679,6 +29417,12 @@ "description": "List environment paths", "name": "paths", "options": [ + { + "help": "Show whether each lifecycle environment is associated with the given Capsule id.", + "name": "content-source-id", + "shortname": null, + "value": "NUMBER" + }, { "help": "Show specified fields or predefined field sets only. (See below)", "name": "fields", @@ -33221,7 +32965,7 @@ "help": "If product certificates should be used to authenticate to a custom CDN.", "name": "custom-cdn-auth-enabled", "shortname": null, - "value": "VALUE" + "value": "BOOLEAN" }, { "help": "Id of the Organization", @@ -34529,6 +34273,12 @@ "shortname": null, "value": "VALUE" }, + { + "help": "Whether Simple Content Access should be enabled for the organization.", + "name": "simple-content-access", + "shortname": null, + "value": "BOOLEAN" + }, { "help": "Capsule names/ids", "name": "smart-proxies", @@ -37384,7 +37134,7 @@ "value": "BOOLEAN" }, { - "help": "Print help -----------------------|-----|---------|----- | ALL | DEFAULT | THIN -----------------------|-----|---------|----- | x | x | x | x | x | x system | x | x | group | x | x | | x | x | | x | x | status | x | x | | x | | | x | | information | x | | view | x | x | environment | x | x | | x | | | x | | | x | | status | x | x | -----------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string string string string Values: mismatched, matched, not_specified string string string date string string boolean boot_time Values: true, false Values: built, pending, token_expired, build_failed text string integer string string integer datetime integer string integer Values: security_needed, errata_needed, updated, unknown Values: ok, error string Values: ok, warning, error string string string string string integer string string boolean string integer string infrastructure_facet.foreman infrastructure_facet.smart_proxy_id Values: reporting, no_report Values: disconnect, sync integer string datetime string string job_invocation.id string job_invocation.result Values: cancelled, failed, pending, success datetime datetime string integer string integer string Values: true, false string string string integer string string string integer string string string string integer string string string string string integer string Values: mismatched, matched, not_specified string integer datetime string string reported.boot_time reported.cores reported.disks_total reported.kernel_version reported.ram reported.sockets reported.virtual Values: true, false string string text Values: mismatched, matched, not_specified string Values: mismatched, matched, not_specified string status.applied integer status.enabled Values: true, false status.failed integer status.failed_restarts integer status.interesting Values: true, false status.pending integer status.restarted integer status.skipped integer string subnet.name text string subnet6.name text string string Values: valid, partial, invalid, unknown, disabled, unsubscribed_hypervisor string Values: reboot_needed, process_restart_needed, updated string string text Values: mismatched, matched, not_specified user.firstname string user.lastname string user.login string user.mail string string usergroup.name string string", + "help": "Print help -----------------------|-----|---------|----- | ALL | DEFAULT | THIN -----------------------|-----|---------|----- | x | x | x | x | x | x system | x | x | group | x | x | | x | x | | x | x | status | x | x | | x | | | x | | information | x | | view | x | x | environment | x | x | | x | | | x | | | x | | status | x | x | -----------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string string string string Values: mismatched, matched, not_specified string string date string string boolean boot_time Values: true, false Values: built, pending, token_expired, build_failed text string integer configuration_status.applied integer configuration_status.enabled Values: true, false configuration_status.failed integer configuration_status.failed_restarts integer configuration_status.interesting Values: true, false configuration_status.pending integer configuration_status.restarted integer configuration_status.skipped integer string string datetime integer string integer Values: security_needed, errata_needed, updated, unknown Values: ok, error string Values: ok, warning, error string string string string string integer string string boolean string integer string infrastructure_facet.foreman Values: true, false infrastructure_facet.smart_proxy_id Values: reporting, no_report Values: disconnect, sync integer string datetime string string job_invocation.id string job_invocation.result Values: cancelled, failed, pending, success datetime datetime string string integer string Values: true, false string string string integer string string string integer string string string string integer string string string string string integer string Values: mismatched, matched, not_specified Values: PXELinux_BIOS, PXELinux_UEFI, Grub_UEFI, Grub2_BIOS, Grub2_ELF, Grub2_UEFI, Grub2_UEFI_SecureBoot, Grub2_UEFI_HTTP, Grub2_UEFI_HTTPS, Grub2_UEFI_HTTPS_SecureBoot, iPXE_Embedded, iPXE_UEFI_HTTP, iPXE_Chain_BIOS, iPXE_Chain_UEFI string integer datetime string string reported.bios_release_date reported.bios_vendor reported.bios_version reported.boot_time reported.cores reported.disks_total reported.kernel_version reported.ram reported.sockets reported.virtual Values: true, false string string Values: full_support, maintenance_support, approaching_end_of_maintenance, extended_support, approaching_end_of_support, support_ended text Values: mismatched, matched, not_specified string Values: mismatched, matched, not_specified string status.applied integer status.enabled Values: true, false status.failed integer status.failed_restarts integer status.interesting Values: true, false status.pending integer status.restarted integer status.skipped integer string subnet.name text string subnet6.name text string string Values: valid, partial, invalid, unknown, disabled, unsubscribed_hypervisor string Values: reboot_needed, process_restart_needed, updated string string text Values: mismatched, matched, not_specified user.firstname string user.lastname string user.login string user.mail string string usergroup.name string string", "name": "help", "shortname": "h", "value": null @@ -38647,7 +38397,7 @@ "value": null }, { - "help": "Print help -------------------------------------------------------------------|-----|-------- | ALL | DEFAULT -------------------------------------------------------------------|-----|-------- environments/name | x | x environments/organization | x | x environments/content views/name | x | x environments/content views/composite | x | x environments/content views/last published | x | x environments/content views/content/hosts | x | x environments/content views/content/products | x | x environments/content views/content/yum repos | x | x environments/content views/content/container image repos | x | x environments/content views/content/packages | x | x environments/content views/content/package groups | x | x environments/content views/content/errata | x | x -------------------------------------------------------------------|-----|-------- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help ---------------------------------------------------------------------------------|-----|-------- | ALL | DEFAULT ---------------------------------------------------------------------------------|-----|-------- environments/name | x | x environments/organization | x | x environments/content views/name | x | x environments/content views/composite | x | x environments/content views/last published | x | x environments/content views/repositories/repository id | x | x environments/content views/repositories/repository name | x | x environments/content views/repositories/content counts/warning | x | x environments/content views/repositories/content counts/packages | x | x environments/content views/repositories/content counts/srpms | x | x environments/content views/repositories/content counts/module streams | x | x environments/content views/repositories/content counts/package groups | x | x environments/content views/repositories/content counts/errata | x | x environments/content views/repositories/content counts/debian packages | x | x environments/content views/repositories/content counts/container tags | x | x environments/content views/repositories/content counts/container ma... | x | x environments/content views/repositories/content counts/container ma... | x | x environments/content views/repositories/content counts/files | x | x environments/content views/repositories/content counts/ansible coll... | x | x environments/content views/repositories/content counts/ostree refs | x | x environments/content views/repositories/content counts/python packages | x | x ---------------------------------------------------------------------------------|-----|-------- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -38710,6 +38460,37 @@ ], "subcommands": [] }, + { + "description": "Reclaim space from all On Demand repositories on a capsule", + "name": "reclaim-space", + "options": [ + { + "help": "Do not wait for the task", + "name": "async", + "shortname": null, + "value": null + }, + { + "help": "Id of the capsule", + "name": "id", + "shortname": null, + "value": "NUMBER" + }, + { + "help": "Name to search by", + "name": "name", + "shortname": null, + "value": "VALUE" + }, + { + "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "name": "help", + "shortname": "h", + "value": null + } + ], + "subcommands": [] + }, { "description": "Remove lifecycle environments from the capsule", "name": "remove-lifecycle-environment", @@ -38922,6 +38703,49 @@ } ], "subcommands": [] + }, + { + "description": "Update content counts for the capsule", + "name": "update-counts", + "options": [ + { + "help": "Do not wait for the task", + "name": "async", + "shortname": null, + "value": null + }, + { + "help": "Id of the capsule", + "name": "id", + "shortname": null, + "value": "NUMBER" + }, + { + "help": "Name to search by", + "name": "name", + "shortname": null, + "value": "VALUE" + }, + { + "help": "Organization name", + "name": "organization", + "shortname": null, + "value": "VALUE" + }, + { + "help": "Organization ID", + "name": "organization-id", + "shortname": null, + "value": "VALUE" + }, + { + "help": "Print help you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "name": "help", + "shortname": "h", + "value": null + } + ], + "subcommands": [] } ] }, @@ -39233,7 +39057,7 @@ "value": null }, { - "help": "Print help -----------------|-----|---------|----- | ALL | DEFAULT | THIN -----------------|-----|---------|----- | x | x | x | x | x | x | x | x | | x | x | | x | x | | x | x | | x | x | Features/name | x | x | Features/version | x | x | Locations/ | x | x | Organizations/ | x | x | at | x | x | at | x | x | -----------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help -----------------|-----|---------|----- | ALL | DEFAULT | THIN -----------------|-----|---------|----- | x | x | x | x | x | x | x | x | | x | x | | x | x | | x | x | count | x | x | Features/name | x | x | Features/version | x | x | Locations/ | x | x | Organizations/ | x | x | at | x | x | at | x | x | -----------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -40112,7 +39936,7 @@ "value": null }, { - "help": "Print help ----------------|-----|-------- | ALL | DEFAULT ----------------|-----|-------- | x | x line | x | x | x | x occurrence | x | x occurrence | x | x | x | x limit | x | x until | x | x | x | x ----------------|-----|-------- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help ----------------|-----|-------- | ALL | DEFAULT ----------------|-----|-------- | x | x line | x | x | x | x occurrence | x | x occurrence | x | x count | x | x | x | x occurrence | x | x occurrence | x | x | x | x limit | x | x limit | x | x until | x | x | x | x | x | x ----------------|-----|-------- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -40191,7 +40015,7 @@ "value": "VALUE" }, { - "help": "Print help ----------|-----|-------- | ALL | DEFAULT ----------|-----|-------- | x | x line | x | x | x | x time | x | x | x | x ----------|-----|-------- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help ----------------|-----|-------- | ALL | DEFAULT ----------------|-----|-------- | x | x line | x | x count | x | x | x | x occurrence | x | x occurrence | x | x | x | x limit | x | x time | x | x | x | x | x | x ----------------|-----|-------- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -40536,7 +40360,7 @@ "value": null }, { - "help": "Print help --------------------------------|-----|---------|----- | ALL | DEFAULT | THIN --------------------------------|-----|---------|----- | x | x | x | x | x | at | x | x | | x | x | status/applied | x | x | status/restarted | x | x | status/failed | x | x | status/restart failures | x | x | status/skipped | x | x | status/pending | x | x | metrics/config_retrieval | x | x | metrics/exec | x | x | metrics/file | x | x | metrics/package | x | x | metrics/service | x | x | metrics/user | x | x | metrics/yumrepo | x | x | metrics/filebucket | x | x | metrics/cron | x | x | metrics/total | x | x | Logs/resource | x | x | Logs/message | x | x | --------------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help --------------------------------|-----|---------|----- | ALL | DEFAULT | THIN --------------------------------|-----|---------|----- | x | x | x | x | x | at | x | x | | x | x | status/applied | x | x | status/restarted | x | x | status/failed | x | x | status/restart failures | x | x | status/skipped | x | x | status/pending | x | x | metrics/config retrieval | x | x | metrics/exec | x | x | metrics/file | x | x | metrics/package | x | x | metrics/service | x | x | metrics/user | x | x | metrics/yumrepo | x | x | metrics/filebucket | x | x | metrics/cron | x | x | metrics/total | x | x | Logs/resource | x | x | Logs/message | x | x | --------------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -41789,12 +41613,6 @@ "shortname": null, "value": "VALUE" }, - { - "help": "Comma-separated list of tags to sync for Container Image repository (Deprecated)", - "name": "docker-tags-whitelist", - "shortname": null, - "value": "LIST" - }, { "help": "Name of the upstream docker repository", "name": "docker-upstream-name", @@ -41850,7 +41668,7 @@ "value": "ENUM" }, { - "help": "List of content units to ignore while syncing a yum repository. Must be subset of srpm", + "help": "List of content units to ignore while syncing a yum repository. Must be subset of srpm,treeinfo", "name": "ignorable-content", "shortname": null, "value": "LIST" @@ -41874,10 +41692,10 @@ "value": "VALUE" }, { - "help": "True if this repository when synced has to be mirrored from the source and stale rpms removed (Deprecated)", - "name": "mirror-on-sync", + "help": "Time to expire yum metadata in seconds. Only relevant for custom yum repositories.", + "name": "metadata-expire", "shortname": null, - "value": "BOOLEAN" + "value": "NUMBER" }, { "help": "Policy to set for mirroring content. Must be one of additive. Possible value(s): 'additive', 'mirror_complete', 'mirror_content_only'", @@ -41916,7 +41734,7 @@ "value": null }, { - "help": "Versionsentifies whether the repository should be disabled on a client with a non-matching OS version. Pass [] to enable regardless of OS version. Maximum length 1; allowed tags are: rhel-6, rhel-7, rhel-8, rhel-9", + "help": "Versionsentifies whether the repository should be unavailable on a client with a non-matching OS version. Pass [] to make repo available for clients regardless of OS version. Maximum length 1; allowed tags are: rhel-6, rhel-7, rhel-8, rhel-9", "name": "os-versions", "shortname": null, "value": "LIST" @@ -42012,6 +41830,12 @@ "description": "Destroy a custom repository", "name": "delete", "options": [ + { + "help": "Delete content view filters that have this repository as the last associated repository. Defaults to true. If false, such filters will now apply to all repositories in the content view.", + "name": "delete-empty-repo-filters", + "shortname": null, + "value": "BOOLEAN" + }, { "help": "", "name": "id", @@ -42128,7 +41952,7 @@ "value": null }, { - "help": "Print help ----------------------------------------------|-----|---------|----- | ALL | DEFAULT | THIN ----------------------------------------------|-----|---------|----- | x | x | x | x | x | x | x | x | | x | x | | x | x | hat repository | x | x | type | x | x | type | x | x | policy | x | x | | x | x | via http | x | x | at | x | x | path | x | x | policy | x | x | repository name | x | x | image tags filter | x | x | repository name | x | x | content units | x | x | proxy/id | x | x | proxy/name | x | x | proxy/http proxy policy | x | x | Product/id | x | x | Product/name | x | x | key/id | x | x | key/name | x | x | Sync/status | x | x | Sync/last sync date | x | x | | x | x | | x | x | counts/packages | x | x | counts/source rpms | x | x | counts/package groups | x | x | counts/errata | x | x | counts/container image manifest lists | x | x | counts/container image manifests | x | x | counts/container image tags | x | x | counts/files | x | x | counts/module streams | x | x | ----------------------------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help ----------------------------------------|-----|---------|----- | ALL | DEFAULT | THIN ----------------------------------------|-----|---------|----- | x | x | x | x | x | x | x | x | | x | x | | x | x | hat repository | x | x | type | x | x | label | x | x | type | x | x | policy | x | x | | x | x | via http | x | x | at | x | x | path | x | x | policy | x | x | expiration | x | x | repository name | x | x | image tags filter | x | x | repository name | x | x | content units | x | x | proxy/id | x | x | proxy/name | x | x | proxy/http proxy policy | x | x | Product/id | x | x | Product/name | x | x | key/id | x | x | key/name | x | x | Sync/status | x | x | Sync/last sync date | x | x | | x | x | | x | x | counts/packages | x | x | counts/srpms | x | x | counts/module streams | x | x | counts/package groups | x | x | counts/errata | x | x | counts/debian packages | x | x | counts/container tags | x | x | counts/container manifests | x | x | counts/container manifest lists | x | x | counts/files | x | x | counts/ansible collections | x | x | counts/ostree refs | x | x | counts/python packages | x | x | ----------------------------------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -42363,7 +42187,7 @@ "value": "VALUE" }, { - "help": "Print help -------------|-----|---------|----- | ALL | DEFAULT | THIN -------------|-----|---------|----- | x | x | x | x | x | x | x | x | type | x | x | | x | x | -------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string string string string integer text string boolean string string string string string string string integer string Values: true, false", + "help": "Print help --------------|-----|---------|----- | ALL | DEFAULT | THIN --------------|-----|---------|----- | x | x | x | x | x | x | x | x | type | x | x | label | x | x | | x | x | --------------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string string string string integer text string boolean string string string string string string string integer string Values: true, false", "name": "help", "shortname": "h", "value": null @@ -42516,7 +42340,7 @@ "value": null }, { - "help": "Force metadata regeneration to proceed. Dangerous when repositories use the 'Complete Mirroring' mirroring policy.", + "help": "Force metadata regeneration to proceed. Dangerous when repositories use the 'Complete Mirroring' mirroring policy", "name": "force", "shortname": null, "value": "BOOLEAN" @@ -42667,6 +42491,12 @@ "description": "Show the available repository types", "name": "types", "options": [ + { + "help": "When set to 'True' repository types that are creatable will be returned", + "name": "creatable", + "shortname": null, + "value": "BOOLEAN" + }, { "help": "Show specified fields or predefined field sets only. (See below)", "name": "fields", @@ -42758,12 +42588,6 @@ "shortname": null, "value": "VALUE" }, - { - "help": "Comma-separated list of tags to sync for Container Image repository (Deprecated)", - "name": "docker-tags-whitelist", - "shortname": null, - "value": "LIST" - }, { "help": "Name of the upstream docker repository", "name": "docker-upstream-name", @@ -42825,7 +42649,7 @@ "value": "NUMBER" }, { - "help": "List of content units to ignore while syncing a yum repository. Must be subset of srpm", + "help": "List of content units to ignore while syncing a yum repository. Must be subset of srpm,treeinfo", "name": "ignorable-content", "shortname": null, "value": "LIST" @@ -42843,10 +42667,10 @@ "value": "LIST" }, { - "help": "True if this repository when synced has to be mirrored from the source and stale rpms removed (Deprecated)", - "name": "mirror-on-sync", + "help": "Time to expire yum metadata in seconds. Only relevant for custom yum repositories.", + "name": "metadata-expire", "shortname": null, - "value": "BOOLEAN" + "value": "NUMBER" }, { "help": "Policy to set for mirroring content. Must be one of additive. Possible value(s): 'additive', 'mirror_complete', 'mirror_content_only'", @@ -42885,7 +42709,7 @@ "value": "VALUE" }, { - "help": "Versionsentifies whether the repository should be disabled on a client with a non-matching OS version. Pass [] to enable regardless of OS version. Maximum length 1; allowed tags are: rhel-6, rhel-7, rhel-8, rhel-9", + "help": "Versionsentifies whether the repository should be unavailable on a client with a non-matching OS version. Pass [] to make repo available for clients regardless of OS version. Maximum length 1; allowed tags are: rhel-6, rhel-7, rhel-8, rhel-9", "name": "os-versions", "shortname": null, "value": "LIST" @@ -43492,6 +43316,12 @@ "shortname": null, "value": null }, + { + "help": "Limit content to Red Hat / custom Possible value(s): 'redhat', 'custom'", + "name": "repository-type", + "shortname": null, + "value": "ENUM" + }, { "help": "Search string", "name": "search", @@ -43511,13 +43341,13 @@ "value": "BOOLEAN" }, { - "help": "If true, return custom repository sets along with redhat repos", + "help": "If true, return custom repository sets along with redhat repos. Will be ignored if repository_type is supplied.", "name": "with-custom", "shortname": null, "value": "BOOLEAN" }, { - "help": "Print help -------|-----|---------|----- | ALL | DEFAULT | THIN -------|-----|---------|----- | x | x | x | x | x | | x | x | x -------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string string string Values: true, false string string string string integer string", + "help": "Print help -------|-----|---------|----- | ALL | DEFAULT | THIN -------|-----|---------|----- | x | x | x | x | x | | x | x | x -------|-----|---------|----- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string string string Values: true, false string string string string integer string Values: true, false", "name": "help", "shortname": "h", "value": null @@ -44977,7 +44807,7 @@ ], "subcommands": [ { - "description": "Disable simple content access for a manifest", + "description": "Disable simple content access for a manifest. WARNING: Simple Content Access will be required for all organizations in Satellite 6.16.", "name": "disable", "options": [ { @@ -45063,7 +44893,7 @@ "subcommands": [] }, { - "description": "Check if the specified organization has Simple Content Access enabled", + "description": "Check if the specified organization has Simple Content Access enabled. WARNING: Simple Content Access will be required for all organizations in Satellite 6.16.", "name": "status", "options": [ { @@ -49085,7 +48915,7 @@ "value": "BOOLEAN" }, { - "help": "For values of type search, this is the resource the value searches in Possible value(s): 'AnsibleRole', 'AnsibleVariable', 'Architecture', 'Audit', 'AuthSource', 'Bookmark', 'ComputeProfile', 'ComputeResource', 'ConfigReport', 'DiscoveryRule', 'Domain', 'ExternalUsergroup', 'FactValue', 'Filter', 'ForemanOpenscap::ArfReport', 'ForemanOpenscap::OvalContent', 'ForemanOpenscap::OvalPolicy', 'ForemanOpenscap::Policy', 'ForemanOpenscap::ScapContent', 'ForemanOpenscap::TailoringFile', 'ForemanTasks::RecurringLogic', 'ForemanTasks::Task', 'ForemanVirtWhoConfigure::Config', 'Host', 'Hostgroup', 'HttpProxy', 'Image', 'InsightsHit', 'JobInvocation', 'JobTemplate', 'Katello::ActivationKey', 'Katello::AlternateContentSource', 'Katello::ContentCredential', 'Katello::ContentView', 'Katello::HostCollection', 'Katello::KTEnvironment', 'Katello::Product', 'Katello::Subscription', 'Katello::SyncPlan', 'KeyPair', 'Location', 'MailNotification', 'Medium', 'Model', 'Operatingsystem', 'Organization', 'Parameter', 'PersonalAccessToken', 'ProvisioningTemplate', 'Ptable', 'Realm', 'RemoteExecutionFeature', 'ReportTemplate', 'Role', 'Setting', 'SmartProxy', 'SshKey', 'Subnet', 'Template', 'TemplateInvocation', 'User', 'Usergroup', 'Webhook', 'WebhookTemplate'", + "help": "For values of type search, this is the resource the value searches in Possible value(s): 'AnsibleRole', 'AnsibleVariable', 'Architecture', 'Audit', 'AuthSource', 'Bookmark', 'ComputeProfile', 'ComputeResource', 'ConfigReport', 'DiscoveryRule', 'Domain', 'ExternalUsergroup', 'FactValue', 'Filter', 'ForemanOpenscap::ArfReport', 'ForemanOpenscap::OvalContent', 'ForemanOpenscap::OvalPolicy', 'ForemanOpenscap::Policy', 'ForemanOpenscap::ScapContent', 'ForemanOpenscap::TailoringFile', 'ForemanTasks::RecurringLogic', 'ForemanTasks::Task', 'ForemanVirtWhoConfigure::Config', 'Host', 'Hostgroup', 'HttpProxy', 'Image', 'InsightsHit', 'JobInvocation', 'JobTemplate', 'Katello::ActivationKey', 'Katello::AlternateContentSource', 'Katello::ContentCredential', 'Katello::ContentView', 'Katello::HostCollection', 'Katello::KTEnvironment', 'Katello::Product', 'Katello::Subscription', 'Katello::SyncPlan', 'KeyPair', 'Location', 'LookupValue', 'MailNotification', 'Medium', 'Model', 'Operatingsystem', 'Organization', 'Parameter', 'PersonalAccessToken', 'ProvisioningTemplate', 'Ptable', 'Realm', 'RemoteExecutionFeature', 'ReportTemplate', 'Role', 'Setting', 'SmartProxy', 'SshKey', 'Subnet', 'Template', 'TemplateInvocation', 'User', 'Usergroup', 'Webhook', 'WebhookTemplate'", "name": "resource-type", "shortname": null, "value": "ENUM" @@ -49449,7 +49279,7 @@ "value": "BOOLEAN" }, { - "help": "For values of type search, this is the resource the value searches in Possible value(s): 'AnsibleRole', 'AnsibleVariable', 'Architecture', 'Audit', 'AuthSource', 'Bookmark', 'ComputeProfile', 'ComputeResource', 'ConfigReport', 'DiscoveryRule', 'Domain', 'ExternalUsergroup', 'FactValue', 'Filter', 'ForemanOpenscap::ArfReport', 'ForemanOpenscap::OvalContent', 'ForemanOpenscap::OvalPolicy', 'ForemanOpenscap::Policy', 'ForemanOpenscap::ScapContent', 'ForemanOpenscap::TailoringFile', 'ForemanTasks::RecurringLogic', 'ForemanTasks::Task', 'ForemanVirtWhoConfigure::Config', 'Host', 'Hostgroup', 'HttpProxy', 'Image', 'InsightsHit', 'JobInvocation', 'JobTemplate', 'Katello::ActivationKey', 'Katello::AlternateContentSource', 'Katello::ContentCredential', 'Katello::ContentView', 'Katello::HostCollection', 'Katello::KTEnvironment', 'Katello::Product', 'Katello::Subscription', 'Katello::SyncPlan', 'KeyPair', 'Location', 'MailNotification', 'Medium', 'Model', 'Operatingsystem', 'Organization', 'Parameter', 'PersonalAccessToken', 'ProvisioningTemplate', 'Ptable', 'Realm', 'RemoteExecutionFeature', 'ReportTemplate', 'Role', 'Setting', 'SmartProxy', 'SshKey', 'Subnet', 'Template', 'TemplateInvocation', 'User', 'Usergroup', 'Webhook', 'WebhookTemplate'", + "help": "For values of type search, this is the resource the value searches in Possible value(s): 'AnsibleRole', 'AnsibleVariable', 'Architecture', 'Audit', 'AuthSource', 'Bookmark', 'ComputeProfile', 'ComputeResource', 'ConfigReport', 'DiscoveryRule', 'Domain', 'ExternalUsergroup', 'FactValue', 'Filter', 'ForemanOpenscap::ArfReport', 'ForemanOpenscap::OvalContent', 'ForemanOpenscap::OvalPolicy', 'ForemanOpenscap::Policy', 'ForemanOpenscap::ScapContent', 'ForemanOpenscap::TailoringFile', 'ForemanTasks::RecurringLogic', 'ForemanTasks::Task', 'ForemanVirtWhoConfigure::Config', 'Host', 'Hostgroup', 'HttpProxy', 'Image', 'InsightsHit', 'JobInvocation', 'JobTemplate', 'Katello::ActivationKey', 'Katello::AlternateContentSource', 'Katello::ContentCredential', 'Katello::ContentView', 'Katello::HostCollection', 'Katello::KTEnvironment', 'Katello::Product', 'Katello::Subscription', 'Katello::SyncPlan', 'KeyPair', 'Location', 'LookupValue', 'MailNotification', 'Medium', 'Model', 'Operatingsystem', 'Organization', 'Parameter', 'PersonalAccessToken', 'ProvisioningTemplate', 'Ptable', 'Realm', 'RemoteExecutionFeature', 'ReportTemplate', 'Role', 'Setting', 'SmartProxy', 'SshKey', 'Subnet', 'Template', 'TemplateInvocation', 'User', 'Usergroup', 'Webhook', 'WebhookTemplate'", "name": "resource-type", "shortname": null, "value": "ENUM" @@ -50053,7 +49883,7 @@ "value": "LIST" }, { - "help": "User's timezone Possible value(s): 'International Date Line West', 'American Samoa', 'Midway Island', 'Hawaii', 'Alaska', 'Pacific Time (US & Canada)', 'Tijuana', 'Arizona', 'Mazatlan', 'Mountain Time (US & Canada)', 'Central America', 'Central Time (US & Canada)', 'Chihuahua', 'Guadalajara', 'Mexico City', 'Monterrey', 'Saskatchewan', 'Bogota', 'Eastern Time (US & Canada)', 'Indiana (East)', 'Lima', 'Quito', 'Atlantic Time (Canada)', 'Caracas', 'Georgetown', 'La Paz', 'Puerto Rico', 'Santiago', 'Newfoundland', 'Brasilia', 'Buenos Aires', 'Greenland', 'Montevideo', 'Mid-Atlantic', 'Azores', 'Cape Verde Is.', 'Casablanca', 'Dublin', 'Edinburgh', 'Lisbon', 'London', 'Monrovia', 'UTC', 'Amsterdam', 'Belgrade', 'Berlin', 'Bern', 'Bratislava', 'Brussels', 'Budapest', 'Copenhagen', 'Ljubljana', 'Madrid', 'Paris', 'Prague', 'Rome', 'Sarajevo', 'Skopje', 'Stockholm', 'Vienna', 'Warsaw', 'West Central Africa', 'Zagreb', 'Zurich', 'Athens', 'Bucharest', 'Cairo', 'Harare', 'Helsinki', 'Jerusalem', 'Kaliningrad', 'Kyiv', 'Pretoria', 'Riga', 'Sofia', 'Tallinn', 'Vilnius', 'Baghdad', 'Istanbul', 'Kuwait', 'Minsk', 'Moscow', 'Nairobi', 'Riyadh', 'St. Petersburg', 'Volgograd', 'Tehran', 'Abu Dhabi', 'Baku', 'Muscat', 'Samara', 'Tbilisi', 'Yerevan', 'Kabul', 'Ekaterinburg', 'Islamabad', 'Karachi', 'Tashkent', 'Chennai', 'Kolkata', 'Mumbai', 'New Delhi', 'Sri Jayawardenepura', 'Kathmandu', 'Almaty', 'Astana', 'Dhaka', 'Urumqi', 'Rangoon', 'Bangkok', 'Hanoi', 'Jakarta', 'Krasnoyarsk', 'Novosibirsk', 'Beijing', 'Chongqing', 'Hong Kong', 'Irkutsk', 'Kuala Lumpur', 'Perth', 'Singapore', 'Taipei', 'Ulaanbaatar', 'Osaka', 'Sapporo', 'Seoul', 'Tokyo', 'Yakutsk', 'Adelaide', 'Darwin', 'Brisbane', 'Canberra', 'Guam', 'Hobart', 'Melbourne', 'Port Moresby', 'Sydney', 'Vladivostok', 'Magadan', 'New Caledonia', 'Solomon Is.', 'Srednekolymsk', 'Auckland', 'Fiji', 'Kamchatka', 'Marshall Is.', 'Wellington', 'Chatham Is.', 'Nuku'alofa', 'Samoa', 'Tokelau Is.'", + "help": "User's timezone Possible value(s): 'International Date Line West', 'American Samoa', 'Midway Island', 'Hawaii', 'Alaska', 'Pacific Time (US & Canada)', 'Tijuana', 'Arizona', 'Mazatlan', 'Mountain Time (US & Canada)', 'Central America', 'Central Time (US & Canada)', 'Chihuahua', 'Guadalajara', 'Mexico City', 'Monterrey', 'Saskatchewan', 'Bogota', 'Eastern Time (US & Canada)', 'Indiana (East)', 'Lima', 'Quito', 'Atlantic Time (Canada)', 'Caracas', 'Georgetown', 'La Paz', 'Puerto Rico', 'Santiago', 'Newfoundland', 'Brasilia', 'Buenos Aires', 'Montevideo', 'Greenland', 'Mid-Atlantic', 'Azores', 'Cape Verde Is.', 'Casablanca', 'Dublin', 'Edinburgh', 'Lisbon', 'London', 'Monrovia', 'UTC', 'Amsterdam', 'Belgrade', 'Berlin', 'Bern', 'Bratislava', 'Brussels', 'Budapest', 'Copenhagen', 'Ljubljana', 'Madrid', 'Paris', 'Prague', 'Rome', 'Sarajevo', 'Skopje', 'Stockholm', 'Vienna', 'Warsaw', 'West Central Africa', 'Zagreb', 'Zurich', 'Athens', 'Bucharest', 'Cairo', 'Harare', 'Helsinki', 'Jerusalem', 'Kaliningrad', 'Kyiv', 'Pretoria', 'Riga', 'Sofia', 'Tallinn', 'Vilnius', 'Baghdad', 'Istanbul', 'Kuwait', 'Minsk', 'Moscow', 'Nairobi', 'Riyadh', 'St. Petersburg', 'Volgograd', 'Tehran', 'Abu Dhabi', 'Baku', 'Muscat', 'Samara', 'Tbilisi', 'Yerevan', 'Kabul', 'Almaty', 'Ekaterinburg', 'Islamabad', 'Karachi', 'Tashkent', 'Chennai', 'Kolkata', 'Mumbai', 'New Delhi', 'Sri Jayawardenepura', 'Kathmandu', 'Astana', 'Dhaka', 'Urumqi', 'Rangoon', 'Bangkok', 'Hanoi', 'Jakarta', 'Krasnoyarsk', 'Novosibirsk', 'Beijing', 'Chongqing', 'Hong Kong', 'Irkutsk', 'Kuala Lumpur', 'Perth', 'Singapore', 'Taipei', 'Ulaanbaatar', 'Osaka', 'Sapporo', 'Seoul', 'Tokyo', 'Yakutsk', 'Adelaide', 'Darwin', 'Brisbane', 'Canberra', 'Guam', 'Hobart', 'Melbourne', 'Port Moresby', 'Sydney', 'Vladivostok', 'Magadan', 'New Caledonia', 'Solomon Is.', 'Srednekolymsk', 'Auckland', 'Fiji', 'Kamchatka', 'Marshall Is.', 'Wellington', 'Chatham Is.', 'Nuku'alofa', 'Samoa', 'Tokelau Is.'", "name": "timezone", "shortname": null, "value": "ENUM" @@ -51625,7 +51455,7 @@ "value": "LIST" }, { - "help": "User's timezone Possible value(s): 'International Date Line West', 'American Samoa', 'Midway Island', 'Hawaii', 'Alaska', 'Pacific Time (US & Canada)', 'Tijuana', 'Arizona', 'Mazatlan', 'Mountain Time (US & Canada)', 'Central America', 'Central Time (US & Canada)', 'Chihuahua', 'Guadalajara', 'Mexico City', 'Monterrey', 'Saskatchewan', 'Bogota', 'Eastern Time (US & Canada)', 'Indiana (East)', 'Lima', 'Quito', 'Atlantic Time (Canada)', 'Caracas', 'Georgetown', 'La Paz', 'Puerto Rico', 'Santiago', 'Newfoundland', 'Brasilia', 'Buenos Aires', 'Greenland', 'Montevideo', 'Mid-Atlantic', 'Azores', 'Cape Verde Is.', 'Casablanca', 'Dublin', 'Edinburgh', 'Lisbon', 'London', 'Monrovia', 'UTC', 'Amsterdam', 'Belgrade', 'Berlin', 'Bern', 'Bratislava', 'Brussels', 'Budapest', 'Copenhagen', 'Ljubljana', 'Madrid', 'Paris', 'Prague', 'Rome', 'Sarajevo', 'Skopje', 'Stockholm', 'Vienna', 'Warsaw', 'West Central Africa', 'Zagreb', 'Zurich', 'Athens', 'Bucharest', 'Cairo', 'Harare', 'Helsinki', 'Jerusalem', 'Kaliningrad', 'Kyiv', 'Pretoria', 'Riga', 'Sofia', 'Tallinn', 'Vilnius', 'Baghdad', 'Istanbul', 'Kuwait', 'Minsk', 'Moscow', 'Nairobi', 'Riyadh', 'St. Petersburg', 'Volgograd', 'Tehran', 'Abu Dhabi', 'Baku', 'Muscat', 'Samara', 'Tbilisi', 'Yerevan', 'Kabul', 'Ekaterinburg', 'Islamabad', 'Karachi', 'Tashkent', 'Chennai', 'Kolkata', 'Mumbai', 'New Delhi', 'Sri Jayawardenepura', 'Kathmandu', 'Almaty', 'Astana', 'Dhaka', 'Urumqi', 'Rangoon', 'Bangkok', 'Hanoi', 'Jakarta', 'Krasnoyarsk', 'Novosibirsk', 'Beijing', 'Chongqing', 'Hong Kong', 'Irkutsk', 'Kuala Lumpur', 'Perth', 'Singapore', 'Taipei', 'Ulaanbaatar', 'Osaka', 'Sapporo', 'Seoul', 'Tokyo', 'Yakutsk', 'Adelaide', 'Darwin', 'Brisbane', 'Canberra', 'Guam', 'Hobart', 'Melbourne', 'Port Moresby', 'Sydney', 'Vladivostok', 'Magadan', 'New Caledonia', 'Solomon Is.', 'Srednekolymsk', 'Auckland', 'Fiji', 'Kamchatka', 'Marshall Is.', 'Wellington', 'Chatham Is.', 'Nuku'alofa', 'Samoa', 'Tokelau Is.'", + "help": "User's timezone Possible value(s): 'International Date Line West', 'American Samoa', 'Midway Island', 'Hawaii', 'Alaska', 'Pacific Time (US & Canada)', 'Tijuana', 'Arizona', 'Mazatlan', 'Mountain Time (US & Canada)', 'Central America', 'Central Time (US & Canada)', 'Chihuahua', 'Guadalajara', 'Mexico City', 'Monterrey', 'Saskatchewan', 'Bogota', 'Eastern Time (US & Canada)', 'Indiana (East)', 'Lima', 'Quito', 'Atlantic Time (Canada)', 'Caracas', 'Georgetown', 'La Paz', 'Puerto Rico', 'Santiago', 'Newfoundland', 'Brasilia', 'Buenos Aires', 'Montevideo', 'Greenland', 'Mid-Atlantic', 'Azores', 'Cape Verde Is.', 'Casablanca', 'Dublin', 'Edinburgh', 'Lisbon', 'London', 'Monrovia', 'UTC', 'Amsterdam', 'Belgrade', 'Berlin', 'Bern', 'Bratislava', 'Brussels', 'Budapest', 'Copenhagen', 'Ljubljana', 'Madrid', 'Paris', 'Prague', 'Rome', 'Sarajevo', 'Skopje', 'Stockholm', 'Vienna', 'Warsaw', 'West Central Africa', 'Zagreb', 'Zurich', 'Athens', 'Bucharest', 'Cairo', 'Harare', 'Helsinki', 'Jerusalem', 'Kaliningrad', 'Kyiv', 'Pretoria', 'Riga', 'Sofia', 'Tallinn', 'Vilnius', 'Baghdad', 'Istanbul', 'Kuwait', 'Minsk', 'Moscow', 'Nairobi', 'Riyadh', 'St. Petersburg', 'Volgograd', 'Tehran', 'Abu Dhabi', 'Baku', 'Muscat', 'Samara', 'Tbilisi', 'Yerevan', 'Kabul', 'Almaty', 'Ekaterinburg', 'Islamabad', 'Karachi', 'Tashkent', 'Chennai', 'Kolkata', 'Mumbai', 'New Delhi', 'Sri Jayawardenepura', 'Kathmandu', 'Astana', 'Dhaka', 'Urumqi', 'Rangoon', 'Bangkok', 'Hanoi', 'Jakarta', 'Krasnoyarsk', 'Novosibirsk', 'Beijing', 'Chongqing', 'Hong Kong', 'Irkutsk', 'Kuala Lumpur', 'Perth', 'Singapore', 'Taipei', 'Ulaanbaatar', 'Osaka', 'Sapporo', 'Seoul', 'Tokyo', 'Yakutsk', 'Adelaide', 'Darwin', 'Brisbane', 'Canberra', 'Guam', 'Hobart', 'Melbourne', 'Port Moresby', 'Sydney', 'Vladivostok', 'Magadan', 'New Caledonia', 'Solomon Is.', 'Srednekolymsk', 'Auckland', 'Fiji', 'Kamchatka', 'Marshall Is.', 'Wellington', 'Chatham Is.', 'Nuku'alofa', 'Samoa', 'Tokelau Is.'", "name": "timezone", "shortname": null, "value": "ENUM" @@ -52794,12 +52624,6 @@ "shortname": null, "value": "BOOLEAN" }, - { - "help": "The frequency of VM-to-host mapping updates for AHV(in seconds)", - "name": "ahv-update-interval", - "shortname": null, - "value": "NUMBER" - }, { "help": "Hypervisor blacklist, applicable only when filtering mode is set to 2. Wildcards and regular expressions are supported, multiple records must be separated by comma.", "name": "blacklist", @@ -53207,7 +53031,7 @@ "value": null }, { - "help": "Print help -----------------------------------------|-----|-------- | ALL | DEFAULT -----------------------------------------|-----|-------- information/id | x | x information/name | x | x information/hypervisor type | x | x information/hypervisor server | x | x information/hypervisor username | x | x information/configuration file | x | x information/ahv prism flavor | x | x information/ahv update frequency | x | x information/enable ahv debug | x | x information/status | x | x Schedule/interval | x | x Schedule/last report at | x | x Connection/satellite server | x | x Connection/hypervisor id | x | x Connection/filtering | x | x Connection/excluded hosts | x | x Connection/filtered hosts | x | x Connection/filter host parents | x | x Connection/exclude host parents | x | x Connection/debug mode | x | x Connection/ignore proxy | x | x proxy/http proxy id | x | x proxy/http proxy name | x | x proxy/http proxy url | x | x Locations/ | x | x Organizations/ | x | x -----------------------------------------|-----|-------- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", + "help": "Print help ----------------------------------------|-----|-------- | ALL | DEFAULT ----------------------------------------|-----|-------- information/id | x | x information/name | x | x information/hypervisor type | x | x information/hypervisor server | x | x information/hypervisor username | x | x information/configuration file | x | x information/ahv prism flavor | x | x information/enable ahv debug | x | x information/status | x | x Schedule/interval | x | x Schedule/last report at | x | x Connection/satellite server | x | x Connection/hypervisor id | x | x Connection/filtering | x | x Connection/excluded hosts | x | x Connection/filtered hosts | x | x Connection/filter host parents | x | x Connection/exclude host parents | x | x Connection/debug mode | x | x Connection/ignore proxy | x | x proxy/http proxy id | x | x proxy/http proxy name | x | x proxy/http proxy url | x | x Locations/ | x | x Organizations/ | x | x ----------------------------------------|-----|-------- you can find option types and the value an option can accept: One of true/false, yes/no, 1/0 Date and time in YYYY-MM-DD HH:MM:SS or ISO 8601 format Possible values are described in the option's description Path to a file Comma-separated list of key=value. JSON is acceptable and preferred way for such parameters Comma separated list of values. Values containing comma should be quoted or escaped with backslash. JSON is acceptable and preferred way for such parameters Any combination of possible values described in the option's description Numeric value. Integer Comma separated list of values defined by a schema. JSON is acceptable and preferred way for such parameters Value described in the option's description. Mostly simple string", "name": "help", "shortname": "h", "value": null @@ -53304,12 +53128,6 @@ "shortname": null, "value": "BOOLEAN" }, - { - "help": "The frequency of VM-to-host mapping updates for AHV(in seconds)", - "name": "ahv-update-interval", - "shortname": null, - "value": "NUMBER" - }, { "help": "Hypervisor blacklist, applicable only when filtering mode is set to 2. Wildcards and regular expressions are supported, multiple records must be separated by comma.", "name": "blacklist", @@ -53506,7 +53324,7 @@ "value": "BOOLEAN" }, { - "help": "Possible value(s): 'actions.katello.content_view.promote_succeeded', 'actions.katello.content_view.publish_succeeded', 'actions.katello.repository.sync_succeeded', 'actions.remote_execution.run_host_job_ansible_configure_cloud_connector_succeeded', 'actions.remote_execution.run_host_job_ansible_enable_web_console_succeeded', 'actions.remote_execution.run_host_job_ansible_run_capsule_upgrade_succeeded', 'actions.remote_execution.run_host_job_ansible_run_host_succeeded', 'actions.remote_execution.run_host_job_ansible_run_insights_plan_succeeded', 'actions.remote_execution.run_host_job_ansible_run_playbook_succeeded', 'actions.remote_execution.run_host_job_foreman_openscap_run_oval_scans_succeeded', 'actions.remote_execution.run_host_job_foreman_openscap_run_scans_succeeded', 'actions.remote_execution.run_host_job_katello_errata_install_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_errata_install_succeeded', 'actions.remote_execution.run_host_job_katello_group_install_succeeded', 'actions.remote_execution.run_host_job_katello_group_remove_succeeded', 'actions.remote_execution.run_host_job_katello_group_update_succeeded', 'actions.remote_execution.run_host_job_katello_host_tracer_resolve_succeeded', 'actions.remote_execution.run_host_job_katello_module_stream_action_succeeded', 'actions.remote_execution.run_host_job_katello_package_install_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_package_install_succeeded', 'actions.remote_execution.run_host_job_katello_package_remove_succeeded', 'actions.remote_execution.run_host_job_katello_package_update_succeeded', 'actions.remote_execution.run_host_job_katello_packages_remove_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_packages_update_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_service_restart_succeeded', 'actions.remote_execution.run_host_job_leapp_preupgrade_succeeded', 'actions.remote_execution.run_host_job_leapp_remediation_plan_succeeded', 'actions.remote_execution.run_host_job_leapp_upgrade_succeeded', 'actions.remote_execution.run_host_job_puppet_run_host_succeeded', 'actions.remote_execution.run_host_job_rh_cloud_connector_run_playbook_succeeded', 'actions.remote_execution.run_host_job_rh_cloud_remediate_hosts_succeeded', 'actions.remote_execution.run_host_job_succeeded', 'build_entered', 'build_exited', 'content_view_created', 'content_view_destroyed', 'content_view_updated', 'domain_created', 'domain_destroyed', 'domain_updated', 'host_created', 'host_destroyed', 'host_updated', 'hostgroup_created', 'hostgroup_destroyed', 'hostgroup_updated', 'model_created', 'model_destroyed', 'model_updated', 'status_changed', 'subnet_created', 'subnet_destroyed', 'subnet_updated', 'user_created', 'user_destroyed', 'user_updated'", + "help": "Possible value(s): 'actions.katello.capsule_content.sync_failed', 'actions.katello.capsule_content.sync_succeeded', 'actions.katello.content_view.promote_failed', 'actions.katello.content_view.promote_succeeded', 'actions.katello.content_view.publish_failed', 'actions.katello.content_view.publish_succeeded', 'actions.katello.repository.sync_failed', 'actions.katello.repository.sync_succeeded', 'actions.remote_execution.run_host_job_ansible_configure_cloud_connector_failed', 'actions.remote_execution.run_host_job_ansible_configure_cloud_connector_succeeded', 'actions.remote_execution.run_host_job_ansible_enable_web_console_failed', 'actions.remote_execution.run_host_job_ansible_enable_web_console_succeeded', 'actions.remote_execution.run_host_job_ansible_run_capsule_upgrade_failed', 'actions.remote_execution.run_host_job_ansible_run_capsule_upgrade_succeeded', 'actions.remote_execution.run_host_job_ansible_run_host_failed', 'actions.remote_execution.run_host_job_ansible_run_host_succeeded', 'actions.remote_execution.run_host_job_ansible_run_insights_plan_failed', 'actions.remote_execution.run_host_job_ansible_run_insights_plan_succeeded', 'actions.remote_execution.run_host_job_ansible_run_playbook_failed', 'actions.remote_execution.run_host_job_ansible_run_playbook_succeeded', 'actions.remote_execution.run_host_job_failed', 'actions.remote_execution.run_host_job_foreman_openscap_run_oval_scans_failed', 'actions.remote_execution.run_host_job_foreman_openscap_run_oval_scans_succeeded', 'actions.remote_execution.run_host_job_foreman_openscap_run_scans_failed', 'actions.remote_execution.run_host_job_foreman_openscap_run_scans_succeeded', 'actions.remote_execution.run_host_job_katello_errata_install_by_search_failed', 'actions.remote_execution.run_host_job_katello_errata_install_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_errata_install_failed', 'actions.remote_execution.run_host_job_katello_errata_install_succeeded', 'actions.remote_execution.run_host_job_katello_group_install_failed', 'actions.remote_execution.run_host_job_katello_group_install_succeeded', 'actions.remote_execution.run_host_job_katello_group_remove_failed', 'actions.remote_execution.run_host_job_katello_group_remove_succeeded', 'actions.remote_execution.run_host_job_katello_group_update_failed', 'actions.remote_execution.run_host_job_katello_group_update_succeeded', 'actions.remote_execution.run_host_job_katello_host_tracer_resolve_failed', 'actions.remote_execution.run_host_job_katello_host_tracer_resolve_succeeded', 'actions.remote_execution.run_host_job_katello_module_stream_action_failed', 'actions.remote_execution.run_host_job_katello_module_stream_action_succeeded', 'actions.remote_execution.run_host_job_katello_package_install_by_search_failed', 'actions.remote_execution.run_host_job_katello_package_install_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_package_install_failed', 'actions.remote_execution.run_host_job_katello_package_install_succeeded', 'actions.remote_execution.run_host_job_katello_package_remove_failed', 'actions.remote_execution.run_host_job_katello_package_remove_succeeded', 'actions.remote_execution.run_host_job_katello_package_update_failed', 'actions.remote_execution.run_host_job_katello_package_update_succeeded', 'actions.remote_execution.run_host_job_katello_packages_remove_by_search_failed', 'actions.remote_execution.run_host_job_katello_packages_remove_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_packages_update_by_search_failed', 'actions.remote_execution.run_host_job_katello_packages_update_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_service_restart_failed', 'actions.remote_execution.run_host_job_katello_service_restart_succeeded', 'actions.remote_execution.run_host_job_leapp_preupgrade_failed', 'actions.remote_execution.run_host_job_leapp_preupgrade_succeeded', 'actions.remote_execution.run_host_job_leapp_remediation_plan_failed', 'actions.remote_execution.run_host_job_leapp_remediation_plan_succeeded', 'actions.remote_execution.run_host_job_leapp_upgrade_failed', 'actions.remote_execution.run_host_job_leapp_upgrade_succeeded', 'actions.remote_execution.run_host_job_puppet_run_host_failed', 'actions.remote_execution.run_host_job_puppet_run_host_succeeded', 'actions.remote_execution.run_host_job_rh_cloud_connector_run_playbook_failed', 'actions.remote_execution.run_host_job_rh_cloud_connector_run_playbook_succeeded', 'actions.remote_execution.run_host_job_rh_cloud_remediate_hosts_failed', 'actions.remote_execution.run_host_job_rh_cloud_remediate_hosts_succeeded', 'actions.remote_execution.run_host_job_run_script_failed', 'actions.remote_execution.run_host_job_run_script_succeeded', 'actions.remote_execution.run_host_job_succeeded', 'actions.remote_execution.run_hosts_job_ansible_configure_cloud_connector_failed', 'actions.remote_execution.run_hosts_job_ansible_configure_cloud_connector_running', 'actions.remote_execution.run_hosts_job_ansible_configure_cloud_connector_succeeded', 'actions.remote_execution.run_hosts_job_ansible_enable_web_console_failed', 'actions.remote_execution.run_hosts_job_ansible_enable_web_console_running', 'actions.remote_execution.run_hosts_job_ansible_enable_web_console_succeeded', 'actions.remote_execution.run_hosts_job_ansible_run_capsule_upgrade_failed', 'actions.remote_execution.run_hosts_job_ansible_run_capsule_upgrade_running', 'actions.remote_execution.run_hosts_job_ansible_run_capsule_upgrade_succeeded', 'actions.remote_execution.run_hosts_job_ansible_run_host_failed', 'actions.remote_execution.run_hosts_job_ansible_run_host_running', 'actions.remote_execution.run_hosts_job_ansible_run_host_succeeded', 'actions.remote_execution.run_hosts_job_ansible_run_insights_plan_failed', 'actions.remote_execution.run_hosts_job_ansible_run_insights_plan_running', 'actions.remote_execution.run_hosts_job_ansible_run_insights_plan_succeeded', 'actions.remote_execution.run_hosts_job_ansible_run_playbook_failed', 'actions.remote_execution.run_hosts_job_ansible_run_playbook_running', 'actions.remote_execution.run_hosts_job_ansible_run_playbook_succeeded', 'actions.remote_execution.run_hosts_job_failed', 'actions.remote_execution.run_hosts_job_foreman_openscap_run_oval_scans_failed', 'actions.remote_execution.run_hosts_job_foreman_openscap_run_oval_scans_running', 'actions.remote_execution.run_hosts_job_foreman_openscap_run_oval_scans_succeeded', 'actions.remote_execution.run_hosts_job_foreman_openscap_run_scans_failed', 'actions.remote_execution.run_hosts_job_foreman_openscap_run_scans_running', 'actions.remote_execution.run_hosts_job_foreman_openscap_run_scans_succeeded', 'actions.remote_execution.run_hosts_job_katello_errata_install_by_search_failed', 'actions.remote_execution.run_hosts_job_katello_errata_install_by_search_running', 'actions.remote_execution.run_hosts_job_katello_errata_install_by_search_succeeded', 'actions.remote_execution.run_hosts_job_katello_errata_install_failed', 'actions.remote_execution.run_hosts_job_katello_errata_install_running', 'actions.remote_execution.run_hosts_job_katello_errata_install_succeeded', 'actions.remote_execution.run_hosts_job_katello_group_install_failed', 'actions.remote_execution.run_hosts_job_katello_group_install_running', 'actions.remote_execution.run_hosts_job_katello_group_install_succeeded', 'actions.remote_execution.run_hosts_job_katello_group_remove_failed', 'actions.remote_execution.run_hosts_job_katello_group_remove_running', 'actions.remote_execution.run_hosts_job_katello_group_remove_succeeded', 'actions.remote_execution.run_hosts_job_katello_group_update_failed', 'actions.remote_execution.run_hosts_job_katello_group_update_running', 'actions.remote_execution.run_hosts_job_katello_group_update_succeeded', 'actions.remote_execution.run_hosts_job_katello_host_tracer_resolve_failed', 'actions.remote_execution.run_hosts_job_katello_host_tracer_resolve_running', 'actions.remote_execution.run_hosts_job_katello_host_tracer_resolve_succeeded', 'actions.remote_execution.run_hosts_job_katello_module_stream_action_failed', 'actions.remote_execution.run_hosts_job_katello_module_stream_action_running', 'actions.remote_execution.run_hosts_job_katello_module_stream_action_succeeded', 'actions.remote_execution.run_hosts_job_katello_package_install_by_search_failed', 'actions.remote_execution.run_hosts_job_katello_package_install_by_search_running', 'actions.remote_execution.run_hosts_job_katello_package_install_by_search_succeeded', 'actions.remote_execution.run_hosts_job_katello_package_install_failed', 'actions.remote_execution.run_hosts_job_katello_package_install_running', 'actions.remote_execution.run_hosts_job_katello_package_install_succeeded', 'actions.remote_execution.run_hosts_job_katello_package_remove_failed', 'actions.remote_execution.run_hosts_job_katello_package_remove_running', 'actions.remote_execution.run_hosts_job_katello_package_remove_succeeded', 'actions.remote_execution.run_hosts_job_katello_package_update_failed', 'actions.remote_execution.run_hosts_job_katello_package_update_running', 'actions.remote_execution.run_hosts_job_katello_package_update_succeeded', 'actions.remote_execution.run_hosts_job_katello_packages_remove_by_search_failed', 'actions.remote_execution.run_hosts_job_katello_packages_remove_by_search_running', 'actions.remote_execution.run_hosts_job_katello_packages_remove_by_search_succeeded', 'actions.remote_execution.run_hosts_job_katello_packages_update_by_search_failed', 'actions.remote_execution.run_hosts_job_katello_packages_update_by_search_running', 'actions.remote_execution.run_hosts_job_katello_packages_update_by_search_succeeded', 'actions.remote_execution.run_hosts_job_katello_service_restart_failed', 'actions.remote_execution.run_hosts_job_katello_service_restart_running', 'actions.remote_execution.run_hosts_job_katello_service_restart_succeeded', 'actions.remote_execution.run_hosts_job_leapp_preupgrade_failed', 'actions.remote_execution.run_hosts_job_leapp_preupgrade_running', 'actions.remote_execution.run_hosts_job_leapp_preupgrade_succeeded', 'actions.remote_execution.run_hosts_job_leapp_remediation_plan_failed', 'actions.remote_execution.run_hosts_job_leapp_remediation_plan_running', 'actions.remote_execution.run_hosts_job_leapp_remediation_plan_succeeded', 'actions.remote_execution.run_hosts_job_leapp_upgrade_failed', 'actions.remote_execution.run_hosts_job_leapp_upgrade_running', 'actions.remote_execution.run_hosts_job_leapp_upgrade_succeeded', 'actions.remote_execution.run_hosts_job_puppet_run_host_failed', 'actions.remote_execution.run_hosts_job_puppet_run_host_running', 'actions.remote_execution.run_hosts_job_puppet_run_host_succeeded', 'actions.remote_execution.run_hosts_job_rh_cloud_connector_run_playbook_failed', 'actions.remote_execution.run_hosts_job_rh_cloud_connector_run_playbook_running', 'actions.remote_execution.run_hosts_job_rh_cloud_connector_run_playbook_succeeded', 'actions.remote_execution.run_hosts_job_rh_cloud_remediate_hosts_failed', 'actions.remote_execution.run_hosts_job_rh_cloud_remediate_hosts_running', 'actions.remote_execution.run_hosts_job_rh_cloud_remediate_hosts_succeeded', 'actions.remote_execution.run_hosts_job_run_script_failed', 'actions.remote_execution.run_hosts_job_run_script_running', 'actions.remote_execution.run_hosts_job_run_script_succeeded', 'actions.remote_execution.run_hosts_job_running', 'actions.remote_execution.run_hosts_job_succeeded', 'build_entered', 'build_exited', 'content_view_created', 'content_view_destroyed', 'content_view_updated', 'domain_created', 'domain_destroyed', 'domain_updated', 'host_created', 'host_destroyed', 'host_facts_updated', 'host_updated', 'hostgroup_created', 'hostgroup_destroyed', 'hostgroup_updated', 'model_created', 'model_destroyed', 'model_updated', 'status_changed', 'subnet_created', 'subnet_destroyed', 'subnet_updated', 'user_created', 'user_destroyed', 'user_updated'", "name": "event", "shortname": null, "value": "ENUM" @@ -53846,7 +53664,7 @@ "value": "BOOLEAN" }, { - "help": "Possible value(s): 'actions.katello.content_view.promote_succeeded', 'actions.katello.content_view.publish_succeeded', 'actions.katello.repository.sync_succeeded', 'actions.remote_execution.run_host_job_ansible_configure_cloud_connector_succeeded', 'actions.remote_execution.run_host_job_ansible_enable_web_console_succeeded', 'actions.remote_execution.run_host_job_ansible_run_capsule_upgrade_succeeded', 'actions.remote_execution.run_host_job_ansible_run_host_succeeded', 'actions.remote_execution.run_host_job_ansible_run_insights_plan_succeeded', 'actions.remote_execution.run_host_job_ansible_run_playbook_succeeded', 'actions.remote_execution.run_host_job_foreman_openscap_run_oval_scans_succeeded', 'actions.remote_execution.run_host_job_foreman_openscap_run_scans_succeeded', 'actions.remote_execution.run_host_job_katello_errata_install_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_errata_install_succeeded', 'actions.remote_execution.run_host_job_katello_group_install_succeeded', 'actions.remote_execution.run_host_job_katello_group_remove_succeeded', 'actions.remote_execution.run_host_job_katello_group_update_succeeded', 'actions.remote_execution.run_host_job_katello_host_tracer_resolve_succeeded', 'actions.remote_execution.run_host_job_katello_module_stream_action_succeeded', 'actions.remote_execution.run_host_job_katello_package_install_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_package_install_succeeded', 'actions.remote_execution.run_host_job_katello_package_remove_succeeded', 'actions.remote_execution.run_host_job_katello_package_update_succeeded', 'actions.remote_execution.run_host_job_katello_packages_remove_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_packages_update_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_service_restart_succeeded', 'actions.remote_execution.run_host_job_leapp_preupgrade_succeeded', 'actions.remote_execution.run_host_job_leapp_remediation_plan_succeeded', 'actions.remote_execution.run_host_job_leapp_upgrade_succeeded', 'actions.remote_execution.run_host_job_puppet_run_host_succeeded', 'actions.remote_execution.run_host_job_rh_cloud_connector_run_playbook_succeeded', 'actions.remote_execution.run_host_job_rh_cloud_remediate_hosts_succeeded', 'actions.remote_execution.run_host_job_succeeded', 'build_entered', 'build_exited', 'content_view_created', 'content_view_destroyed', 'content_view_updated', 'domain_created', 'domain_destroyed', 'domain_updated', 'host_created', 'host_destroyed', 'host_updated', 'hostgroup_created', 'hostgroup_destroyed', 'hostgroup_updated', 'model_created', 'model_destroyed', 'model_updated', 'status_changed', 'subnet_created', 'subnet_destroyed', 'subnet_updated', 'user_created', 'user_destroyed', 'user_updated'", + "help": "Possible value(s): 'actions.katello.capsule_content.sync_failed', 'actions.katello.capsule_content.sync_succeeded', 'actions.katello.content_view.promote_failed', 'actions.katello.content_view.promote_succeeded', 'actions.katello.content_view.publish_failed', 'actions.katello.content_view.publish_succeeded', 'actions.katello.repository.sync_failed', 'actions.katello.repository.sync_succeeded', 'actions.remote_execution.run_host_job_ansible_configure_cloud_connector_failed', 'actions.remote_execution.run_host_job_ansible_configure_cloud_connector_succeeded', 'actions.remote_execution.run_host_job_ansible_enable_web_console_failed', 'actions.remote_execution.run_host_job_ansible_enable_web_console_succeeded', 'actions.remote_execution.run_host_job_ansible_run_capsule_upgrade_failed', 'actions.remote_execution.run_host_job_ansible_run_capsule_upgrade_succeeded', 'actions.remote_execution.run_host_job_ansible_run_host_failed', 'actions.remote_execution.run_host_job_ansible_run_host_succeeded', 'actions.remote_execution.run_host_job_ansible_run_insights_plan_failed', 'actions.remote_execution.run_host_job_ansible_run_insights_plan_succeeded', 'actions.remote_execution.run_host_job_ansible_run_playbook_failed', 'actions.remote_execution.run_host_job_ansible_run_playbook_succeeded', 'actions.remote_execution.run_host_job_failed', 'actions.remote_execution.run_host_job_foreman_openscap_run_oval_scans_failed', 'actions.remote_execution.run_host_job_foreman_openscap_run_oval_scans_succeeded', 'actions.remote_execution.run_host_job_foreman_openscap_run_scans_failed', 'actions.remote_execution.run_host_job_foreman_openscap_run_scans_succeeded', 'actions.remote_execution.run_host_job_katello_errata_install_by_search_failed', 'actions.remote_execution.run_host_job_katello_errata_install_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_errata_install_failed', 'actions.remote_execution.run_host_job_katello_errata_install_succeeded', 'actions.remote_execution.run_host_job_katello_group_install_failed', 'actions.remote_execution.run_host_job_katello_group_install_succeeded', 'actions.remote_execution.run_host_job_katello_group_remove_failed', 'actions.remote_execution.run_host_job_katello_group_remove_succeeded', 'actions.remote_execution.run_host_job_katello_group_update_failed', 'actions.remote_execution.run_host_job_katello_group_update_succeeded', 'actions.remote_execution.run_host_job_katello_host_tracer_resolve_failed', 'actions.remote_execution.run_host_job_katello_host_tracer_resolve_succeeded', 'actions.remote_execution.run_host_job_katello_module_stream_action_failed', 'actions.remote_execution.run_host_job_katello_module_stream_action_succeeded', 'actions.remote_execution.run_host_job_katello_package_install_by_search_failed', 'actions.remote_execution.run_host_job_katello_package_install_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_package_install_failed', 'actions.remote_execution.run_host_job_katello_package_install_succeeded', 'actions.remote_execution.run_host_job_katello_package_remove_failed', 'actions.remote_execution.run_host_job_katello_package_remove_succeeded', 'actions.remote_execution.run_host_job_katello_package_update_failed', 'actions.remote_execution.run_host_job_katello_package_update_succeeded', 'actions.remote_execution.run_host_job_katello_packages_remove_by_search_failed', 'actions.remote_execution.run_host_job_katello_packages_remove_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_packages_update_by_search_failed', 'actions.remote_execution.run_host_job_katello_packages_update_by_search_succeeded', 'actions.remote_execution.run_host_job_katello_service_restart_failed', 'actions.remote_execution.run_host_job_katello_service_restart_succeeded', 'actions.remote_execution.run_host_job_leapp_preupgrade_failed', 'actions.remote_execution.run_host_job_leapp_preupgrade_succeeded', 'actions.remote_execution.run_host_job_leapp_remediation_plan_failed', 'actions.remote_execution.run_host_job_leapp_remediation_plan_succeeded', 'actions.remote_execution.run_host_job_leapp_upgrade_failed', 'actions.remote_execution.run_host_job_leapp_upgrade_succeeded', 'actions.remote_execution.run_host_job_puppet_run_host_failed', 'actions.remote_execution.run_host_job_puppet_run_host_succeeded', 'actions.remote_execution.run_host_job_rh_cloud_connector_run_playbook_failed', 'actions.remote_execution.run_host_job_rh_cloud_connector_run_playbook_succeeded', 'actions.remote_execution.run_host_job_rh_cloud_remediate_hosts_failed', 'actions.remote_execution.run_host_job_rh_cloud_remediate_hosts_succeeded', 'actions.remote_execution.run_host_job_run_script_failed', 'actions.remote_execution.run_host_job_run_script_succeeded', 'actions.remote_execution.run_host_job_succeeded', 'actions.remote_execution.run_hosts_job_ansible_configure_cloud_connector_failed', 'actions.remote_execution.run_hosts_job_ansible_configure_cloud_connector_running', 'actions.remote_execution.run_hosts_job_ansible_configure_cloud_connector_succeeded', 'actions.remote_execution.run_hosts_job_ansible_enable_web_console_failed', 'actions.remote_execution.run_hosts_job_ansible_enable_web_console_running', 'actions.remote_execution.run_hosts_job_ansible_enable_web_console_succeeded', 'actions.remote_execution.run_hosts_job_ansible_run_capsule_upgrade_failed', 'actions.remote_execution.run_hosts_job_ansible_run_capsule_upgrade_running', 'actions.remote_execution.run_hosts_job_ansible_run_capsule_upgrade_succeeded', 'actions.remote_execution.run_hosts_job_ansible_run_host_failed', 'actions.remote_execution.run_hosts_job_ansible_run_host_running', 'actions.remote_execution.run_hosts_job_ansible_run_host_succeeded', 'actions.remote_execution.run_hosts_job_ansible_run_insights_plan_failed', 'actions.remote_execution.run_hosts_job_ansible_run_insights_plan_running', 'actions.remote_execution.run_hosts_job_ansible_run_insights_plan_succeeded', 'actions.remote_execution.run_hosts_job_ansible_run_playbook_failed', 'actions.remote_execution.run_hosts_job_ansible_run_playbook_running', 'actions.remote_execution.run_hosts_job_ansible_run_playbook_succeeded', 'actions.remote_execution.run_hosts_job_failed', 'actions.remote_execution.run_hosts_job_foreman_openscap_run_oval_scans_failed', 'actions.remote_execution.run_hosts_job_foreman_openscap_run_oval_scans_running', 'actions.remote_execution.run_hosts_job_foreman_openscap_run_oval_scans_succeeded', 'actions.remote_execution.run_hosts_job_foreman_openscap_run_scans_failed', 'actions.remote_execution.run_hosts_job_foreman_openscap_run_scans_running', 'actions.remote_execution.run_hosts_job_foreman_openscap_run_scans_succeeded', 'actions.remote_execution.run_hosts_job_katello_errata_install_by_search_failed', 'actions.remote_execution.run_hosts_job_katello_errata_install_by_search_running', 'actions.remote_execution.run_hosts_job_katello_errata_install_by_search_succeeded', 'actions.remote_execution.run_hosts_job_katello_errata_install_failed', 'actions.remote_execution.run_hosts_job_katello_errata_install_running', 'actions.remote_execution.run_hosts_job_katello_errata_install_succeeded', 'actions.remote_execution.run_hosts_job_katello_group_install_failed', 'actions.remote_execution.run_hosts_job_katello_group_install_running', 'actions.remote_execution.run_hosts_job_katello_group_install_succeeded', 'actions.remote_execution.run_hosts_job_katello_group_remove_failed', 'actions.remote_execution.run_hosts_job_katello_group_remove_running', 'actions.remote_execution.run_hosts_job_katello_group_remove_succeeded', 'actions.remote_execution.run_hosts_job_katello_group_update_failed', 'actions.remote_execution.run_hosts_job_katello_group_update_running', 'actions.remote_execution.run_hosts_job_katello_group_update_succeeded', 'actions.remote_execution.run_hosts_job_katello_host_tracer_resolve_failed', 'actions.remote_execution.run_hosts_job_katello_host_tracer_resolve_running', 'actions.remote_execution.run_hosts_job_katello_host_tracer_resolve_succeeded', 'actions.remote_execution.run_hosts_job_katello_module_stream_action_failed', 'actions.remote_execution.run_hosts_job_katello_module_stream_action_running', 'actions.remote_execution.run_hosts_job_katello_module_stream_action_succeeded', 'actions.remote_execution.run_hosts_job_katello_package_install_by_search_failed', 'actions.remote_execution.run_hosts_job_katello_package_install_by_search_running', 'actions.remote_execution.run_hosts_job_katello_package_install_by_search_succeeded', 'actions.remote_execution.run_hosts_job_katello_package_install_failed', 'actions.remote_execution.run_hosts_job_katello_package_install_running', 'actions.remote_execution.run_hosts_job_katello_package_install_succeeded', 'actions.remote_execution.run_hosts_job_katello_package_remove_failed', 'actions.remote_execution.run_hosts_job_katello_package_remove_running', 'actions.remote_execution.run_hosts_job_katello_package_remove_succeeded', 'actions.remote_execution.run_hosts_job_katello_package_update_failed', 'actions.remote_execution.run_hosts_job_katello_package_update_running', 'actions.remote_execution.run_hosts_job_katello_package_update_succeeded', 'actions.remote_execution.run_hosts_job_katello_packages_remove_by_search_failed', 'actions.remote_execution.run_hosts_job_katello_packages_remove_by_search_running', 'actions.remote_execution.run_hosts_job_katello_packages_remove_by_search_succeeded', 'actions.remote_execution.run_hosts_job_katello_packages_update_by_search_failed', 'actions.remote_execution.run_hosts_job_katello_packages_update_by_search_running', 'actions.remote_execution.run_hosts_job_katello_packages_update_by_search_succeeded', 'actions.remote_execution.run_hosts_job_katello_service_restart_failed', 'actions.remote_execution.run_hosts_job_katello_service_restart_running', 'actions.remote_execution.run_hosts_job_katello_service_restart_succeeded', 'actions.remote_execution.run_hosts_job_leapp_preupgrade_failed', 'actions.remote_execution.run_hosts_job_leapp_preupgrade_running', 'actions.remote_execution.run_hosts_job_leapp_preupgrade_succeeded', 'actions.remote_execution.run_hosts_job_leapp_remediation_plan_failed', 'actions.remote_execution.run_hosts_job_leapp_remediation_plan_running', 'actions.remote_execution.run_hosts_job_leapp_remediation_plan_succeeded', 'actions.remote_execution.run_hosts_job_leapp_upgrade_failed', 'actions.remote_execution.run_hosts_job_leapp_upgrade_running', 'actions.remote_execution.run_hosts_job_leapp_upgrade_succeeded', 'actions.remote_execution.run_hosts_job_puppet_run_host_failed', 'actions.remote_execution.run_hosts_job_puppet_run_host_running', 'actions.remote_execution.run_hosts_job_puppet_run_host_succeeded', 'actions.remote_execution.run_hosts_job_rh_cloud_connector_run_playbook_failed', 'actions.remote_execution.run_hosts_job_rh_cloud_connector_run_playbook_running', 'actions.remote_execution.run_hosts_job_rh_cloud_connector_run_playbook_succeeded', 'actions.remote_execution.run_hosts_job_rh_cloud_remediate_hosts_failed', 'actions.remote_execution.run_hosts_job_rh_cloud_remediate_hosts_running', 'actions.remote_execution.run_hosts_job_rh_cloud_remediate_hosts_succeeded', 'actions.remote_execution.run_hosts_job_run_script_failed', 'actions.remote_execution.run_hosts_job_run_script_running', 'actions.remote_execution.run_hosts_job_run_script_succeeded', 'actions.remote_execution.run_hosts_job_running', 'actions.remote_execution.run_hosts_job_succeeded', 'build_entered', 'build_exited', 'content_view_created', 'content_view_destroyed', 'content_view_updated', 'domain_created', 'domain_destroyed', 'domain_updated', 'host_created', 'host_destroyed', 'host_facts_updated', 'host_updated', 'hostgroup_created', 'hostgroup_destroyed', 'hostgroup_updated', 'model_created', 'model_destroyed', 'model_updated', 'status_changed', 'subnet_created', 'subnet_destroyed', 'subnet_updated', 'user_created', 'user_destroyed', 'user_updated'", "name": "event", "shortname": null, "value": "ENUM" diff --git a/tests/foreman/destructive/test_ansible.py b/tests/foreman/destructive/test_ansible.py index 0ff8fc67a1d..b57ba89c58f 100644 --- a/tests/foreman/destructive/test_ansible.py +++ b/tests/foreman/destructive/test_ansible.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: Ansible +:CaseComponent: Ansible-ConfigurationManagement :Team: Rocket @@ -13,7 +13,7 @@ """ import pytest -pytestmark = pytest.mark.destructive +pytestmark = [pytest.mark.destructive, pytest.mark.upgrade] def test_positive_persistent_ansible_cfg_change(target_sat): @@ -21,10 +21,6 @@ def test_positive_persistent_ansible_cfg_change(target_sat): :id: c22fcd47-8627-4230-aa1f-7d4fc8517a0e - :BZ: 1786358 - - :customerscenario: true - :steps: 1. Update value in ansible.cfg. 2. Verify value is updated in the file. @@ -33,6 +29,10 @@ def test_positive_persistent_ansible_cfg_change(target_sat): :expectedresults: Changes in ansible.cfg are persistent after running "satellite-installer". + + :BZ: 1786358 + + :customerscenario: true """ ansible_cfg = '/etc/ansible/ansible.cfg' param = 'local_tmp = /tmp' @@ -49,7 +49,6 @@ def test_positive_import_all_roles(target_sat): :id: 53fe3857-a08f-493d-93c7-3fed331ed391 :steps: - 1. Navigate to the Configure > Roles page. 2. Click the `Import from [hostname]` button. 3. Get total number of importable roles from pagination. diff --git a/tests/foreman/destructive/test_capsule.py b/tests/foreman/destructive/test_capsule.py index 40935c114b9..1cc63f4fbbe 100644 --- a/tests/foreman/destructive/test_capsule.py +++ b/tests/foreman/destructive/test_capsule.py @@ -1,10 +1,10 @@ """Test class for the capsule CLI. -:Requirement: Capsule +:Requirement: ForemanProxy :CaseAutomation: Automated -:CaseComponent: Capsule +:CaseComponent: ForemanProxy :Team: Platform diff --git a/tests/foreman/destructive/test_capsule_loadbalancer.py b/tests/foreman/destructive/test_capsule_loadbalancer.py index a0675abec2b..cf36aac8a9f 100644 --- a/tests/foreman/destructive/test_capsule_loadbalancer.py +++ b/tests/foreman/destructive/test_capsule_loadbalancer.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: Capsule +:CaseComponent: ForemanProxy :Team: Platform @@ -14,6 +14,7 @@ import pytest from wrapanapi import VmState +from robottelo import constants from robottelo.config import settings from robottelo.constants import CLIENT_PORT, DataFile from robottelo.utils.installer import InstallerCommand @@ -22,34 +23,50 @@ @pytest.fixture(scope='module') -def content_for_client(module_target_sat, module_org, module_lce, module_cv, module_ak): +def content_for_client(module_target_sat, module_sca_manifest_org, module_lce, module_cv): """Setup content to be used by haproxy and client :return: Activation key, client lifecycle environment(used by setup_capsules()) """ - module_target_sat.cli_factory.setup_org_for_a_custom_repo( - { - 'url': settings.repos.RHEL7_OS, - 'organization-id': module_org.id, - 'content-view-id': module_cv.id, - 'lifecycle-environment-id': module_lce.id, - 'activationkey-id': module_ak.id, - } - ) - return {'client_ak': module_ak, 'client_lce': module_lce} + rhel_ver = settings.content_host.default_rhel_version + baseos = f'rhel{rhel_ver}_bos' + appstream = f'rhel{rhel_ver}_aps' + + rh_repos = [] + for repo in [baseos, appstream]: + synced_repo_id = module_target_sat.api_factory.enable_sync_redhat_repo( + constants.REPOS[repo], module_sca_manifest_org.id + ) + repo = module_target_sat.api.Repository(id=synced_repo_id).read() + rh_repos.append(repo) + + module_cv.repository = rh_repos + module_cv.update(['repository']) + module_cv.publish() + module_cv = module_cv.read() + cvv = module_cv.version[0] + cvv.promote(data={'environment_ids': module_lce.id}) + module_cv = module_cv.read() + ak = module_target_sat.api.ActivationKey( + content_view=module_cv, + environment=module_lce, + organization=module_sca_manifest_org, + ).create() + + return {'client_ak': ak, 'client_lce': module_lce} @pytest.fixture(scope='module') def setup_capsules( module_org, - rhel7_contenthost_module, + module_rhel_contenthost, module_lb_capsule, module_target_sat, content_for_client, ): """Install capsules with loadbalancer options""" - extra_cert_var = {'foreman-proxy-cname': rhel7_contenthost_module.hostname} - extra_installer_var = {'certs-cname': rhel7_contenthost_module.hostname} + extra_cert_var = {'foreman-proxy-cname': module_rhel_contenthost.hostname} + extra_installer_var = {'certs-cname': module_rhel_contenthost.hostname} for capsule in module_lb_capsule: capsule.register_to_cdn() @@ -92,20 +109,20 @@ def setup_capsules( @pytest.fixture(scope='module') def setup_haproxy( module_org, - rhel7_contenthost_module, + module_rhel_contenthost, content_for_client, module_target_sat, setup_capsules, ): """Install and configure haproxy and setup logging""" - haproxy = rhel7_contenthost_module + haproxy = module_rhel_contenthost # Using same AK for haproxy just for packages haproxy_ak = content_for_client['client_ak'] haproxy.execute('firewall-cmd --add-service RH-Satellite-6-capsule') haproxy.execute('firewall-cmd --runtime-to-permanent') haproxy.install_katello_ca(module_target_sat) haproxy.register_contenthost(module_org.label, haproxy_ak.name) - result = haproxy.execute('yum install haproxy policycoreutils-python -y') + result = haproxy.execute('yum install haproxy policycoreutils-python-utils -y') assert result.status == 0 haproxy.execute('rm -f /etc/haproxy/haproxy.cfg') haproxy.session.sftp_write( @@ -171,8 +188,9 @@ def loadbalancer_setup( @pytest.mark.e2e @pytest.mark.tier1 +@pytest.mark.rhel_ver_list([settings.content_host.default_rhel_version]) def test_loadbalancer_install_package( - loadbalancer_setup, setup_capsules, rhel7_contenthost, module_org, module_location, request + loadbalancer_setup, setup_capsules, rhel_contenthost, module_org, module_location, request ): r"""Install packages on a content host regardless of the registered capsule being available @@ -191,8 +209,9 @@ def test_loadbalancer_install_package( registration. """ + # Register content host - result = rhel7_contenthost.register( + result = rhel_contenthost.register( org=module_org, loc=module_location, activation_keys=loadbalancer_setup['content_for_client']['client_ak'].name, @@ -202,15 +221,15 @@ def test_loadbalancer_install_package( assert result.status == 0, f'Failed to register host: {result.stderr}' # Try package installation - result = rhel7_contenthost.execute('yum install -y tree') + result = rhel_contenthost.execute('yum install -y tree') assert result.status == 0 hosts = loadbalancer_setup['module_target_sat'].cli.Host.list( {'organization-id': loadbalancer_setup['module_org'].id} ) - assert rhel7_contenthost.hostname in [host['name'] for host in hosts] + assert rhel_contenthost.hostname in [host['name'] for host in hosts] - result = rhel7_contenthost.execute('rpm -qa | grep katello-ca-consumer') + result = rhel_contenthost.execute('rpm -qa | grep katello-ca-consumer') # Find which capsule the host is registered to since it's RoundRobin # The following also asserts the above result @@ -219,22 +238,21 @@ def test_loadbalancer_install_package( if loadbalancer_setup['setup_capsules']['capsule_1'].hostname in result.stdout else loadbalancer_setup['setup_capsules']['capsule_2'] ) + request.addfinalizer( + lambda: registered_to_capsule.power_control(state=VmState.RUNNING, ensure=True) + ) # Remove the packages from the client - result = rhel7_contenthost.execute('yum remove -y tree') + result = rhel_contenthost.execute('yum remove -y tree') assert result.status == 0 # Power off the capsule that the client is registered to registered_to_capsule.power_control(state=VmState.STOPPED, ensure=True) # Try package installation again - result = rhel7_contenthost.execute('yum install -y tree') + result = rhel_contenthost.execute('yum install -y tree') assert result.status == 0 - @request.addfinalizer - def _finalize(): - registered_to_capsule.power_control(state=VmState.RUNNING, ensure=True) - @pytest.mark.rhel_ver_match('[^6]') @pytest.mark.tier1 @@ -273,7 +291,7 @@ def test_client_register_through_lb( loadbalancer_setup['setup_haproxy']['haproxy'].hostname in rhel_contenthost.subscription_config['server']['hostname'] ) - assert CLIENT_PORT == rhel_contenthost.subscription_config['server']['port'] + assert rhel_contenthost.subscription_config['server']['port'] == CLIENT_PORT assert loadbalancer_setup['module_target_sat'].cli.Host.info( {'name': rhel_contenthost.hostname}, output_format='json' )['content-information']['content-source']['name'] in [ @@ -294,7 +312,7 @@ def test_client_register_through_lb( loadbalancer_setup['setup_haproxy']['haproxy'].hostname in rhel_contenthost.subscription_config['server']['hostname'] ) - assert CLIENT_PORT == rhel_contenthost.subscription_config['server']['port'] + assert rhel_contenthost.subscription_config['server']['port'] == CLIENT_PORT hosts = loadbalancer_setup['module_target_sat'].cli.Host.list( {'organization-id': loadbalancer_setup['module_org'].id} diff --git a/tests/foreman/destructive/test_capsulecontent.py b/tests/foreman/destructive/test_capsulecontent.py index 12f3455c08d..d060ae29c78 100644 --- a/tests/foreman/destructive/test_capsulecontent.py +++ b/tests/foreman/destructive/test_capsulecontent.py @@ -23,7 +23,7 @@ @pytest.mark.tier4 @pytest.mark.skip_if_not_set('capsule') def test_positive_sync_without_deadlock( - target_sat, large_capsule_configured, function_entitlement_manifest_org + target_sat, large_capsule_configured, function_sca_manifest_org ): """Synchronize one bigger repo published in multiple CVs to a blank Capsule. Assert that the sync task succeeds and no deadlock happens. @@ -54,7 +54,7 @@ def test_positive_sync_without_deadlock( # the lower rpms count. When the BZ is fixed, reconsider upscale to RHEL7 repo or similar. repo_id = target_sat.api_factory.enable_rhrepo_and_fetchid( basearch=constants.DEFAULT_ARCHITECTURE, - org_id=function_entitlement_manifest_org.id, + org_id=function_sca_manifest_org.id, product=constants.REPOS['rhscl7']['product'], repo=constants.REPOS['rhscl7']['name'], reposet=constants.REPOSET['rhscl7'], @@ -63,7 +63,7 @@ def test_positive_sync_without_deadlock( repo = target_sat.api.Repository(id=repo_id).read() repo.sync(timeout='60m') - cv = target_sat.publish_content_view(function_entitlement_manifest_org, repo) + cv = target_sat.publish_content_view(function_sca_manifest_org, repo) for _ in range(4): copy_id = target_sat.api.ContentView(id=cv.id).copy(data={'name': gen_alpha()})['id'] @@ -75,9 +75,9 @@ def test_positive_sync_without_deadlock( proxy.update(['download_policy']) nailgun_capsule = large_capsule_configured.nailgun_capsule - lce = target_sat.api.LifecycleEnvironment( - organization=function_entitlement_manifest_org - ).search(query={'search': f'name={constants.ENVIRONMENT}'})[0] + lce = target_sat.api.LifecycleEnvironment(organization=function_sca_manifest_org).search( + query={'search': f'name={constants.ENVIRONMENT}'} + )[0] nailgun_capsule.content_add_lifecycle_environment(data={'environment_id': lce.id}) result = nailgun_capsule.content_lifecycle_environments() assert len(result['results']) == 1 @@ -89,7 +89,7 @@ def test_positive_sync_without_deadlock( @pytest.mark.tier4 @pytest.mark.skip_if_not_set('capsule') def test_positive_sync_without_deadlock_after_rpm_trim_changelog( - target_sat, capsule_configured, function_entitlement_manifest_org + target_sat, capsule_configured, function_sca_manifest_org ): """Promote a CV published with larger repos into multiple LCEs, assign LCEs to blank Capsule. Assert that the sync task succeeds and no deadlock happens. @@ -115,7 +115,7 @@ def test_positive_sync_without_deadlock_after_rpm_trim_changelog( :BZ: 2170535, 2218661 """ - org = function_entitlement_manifest_org + org = function_sca_manifest_org rh_repos = [] tasks = [] LCE_COUNT = 10 diff --git a/tests/foreman/destructive/test_clone.py b/tests/foreman/destructive/test_clone.py index fade8abcf6a..1ad4cd25f20 100644 --- a/tests/foreman/destructive/test_clone.py +++ b/tests/foreman/destructive/test_clone.py @@ -15,13 +15,18 @@ from robottelo import constants from robottelo.config import settings -from robottelo.hosts import Satellite +from robottelo.hosts import Satellite, get_sat_rhel_version SSH_PASS = settings.server.ssh_password pytestmark = pytest.mark.destructive @pytest.mark.e2e +@pytest.mark.parametrize( + "sat_ready_rhel", + [8, 9] if get_sat_rhel_version().major < 9 else [9], + indirect=True, +) @pytest.mark.parametrize('backup_type', ['online', 'offline']) @pytest.mark.parametrize('skip_pulp', [False, True], ids=['include_pulp', 'skip_pulp']) def test_positive_clone_backup( @@ -89,8 +94,8 @@ def test_positive_clone_backup( # Enabling repositories for repo in getattr(constants, f"OHSNAP_RHEL{rhel_version}_REPOS"): sat_ready_rhel.enable_repo(repo, force=True) - # Enabling satellite module - assert sat_ready_rhel.execute(f'dnf module enable -y satellite:el{rhel_version}').status == 0 + # Enabling satellite module for RHEL8 + sat_ready_rhel.enable_satellite_or_capsule_module_for_rhel8() # Install satellite-clone assert sat_ready_rhel.execute('yum install satellite-clone -y').status == 0 # Disabling CDN repos as we install from dogfdood diff --git a/tests/foreman/destructive/test_contentview.py b/tests/foreman/destructive/test_contentview.py index 03af433a939..d6b39f7556b 100644 --- a/tests/foreman/destructive/test_contentview.py +++ b/tests/foreman/destructive/test_contentview.py @@ -11,6 +11,9 @@ :CaseImportance: High """ +import random +from time import sleep + from nailgun.entity_mixins import TaskFailedError import pytest @@ -19,73 +22,81 @@ pytestmark = pytest.mark.destructive +@pytest.fixture(scope='module') +def module_big_repos(module_target_sat, module_sca_manifest_org): + """Enables and syncs three bigger RH repos""" + repos = [] + for tag in ['rhel7_optional', 'rhel7_extra', 'rhel7_sup']: + id = module_target_sat.api_factory.enable_rhrepo_and_fetchid( + basearch=constants.DEFAULT_ARCHITECTURE, + org_id=module_sca_manifest_org.id, + product=constants.REPOS[tag]['product'], + repo=constants.REPOS[tag]['name'], + reposet=constants.REPOS[tag]['reposet'], + releasever=constants.REPOS[tag]['releasever'], + ) + repo = module_target_sat.api.Repository(id=id).read() + repos.append(repo) + repo.sync(synchronous=False) + module_target_sat.wait_for_tasks( + search_query=( + f'label = Actions::Katello::Repository::Sync and organization_id = {module_sca_manifest_org.id}' + ), + poll_timeout=750, + search_rate=10, + max_tries=75, + ) + return repos + + @pytest.mark.tier4 @pytest.mark.run_in_one_thread -def test_positive_reboot_recover_cv_publish(target_sat, function_entitlement_manifest_org): +@pytest.mark.parametrize('reboot', [True, False], ids=['vm_reboot', 'fm_restart']) +def test_positive_reboot_recover_cv_publish( + module_target_sat, module_sca_manifest_org, module_big_repos, reboot +): """Reboot the Satellite during publish and resume publishing :id: cceae727-81db-40a4-9c26-05ca6e93464e - :steps: - 1. Create and publish a Content View - 2. Reboot the Satellite while publish is running - 3. Check Foreman Tasks + :parametrized: yes - :expectedresults: Publish continues after reboot and finishes successfully + :setup: + 1. Enable and sync 3 bigger RH repos. - :CaseImportance: High + :steps: + 1. Create and publish a Content View with the setup repos. + 2. Reboot or restart the Satellite while publish is running. + 3. Check Foreman Tasks. - :CaseAutomation: Automated + :expectedresults: Publish continues after reboot/restart and finishes successfully. """ - org = function_entitlement_manifest_org - rhel7_extra = target_sat.api_factory.enable_rhrepo_and_fetchid( - basearch='x86_64', - org_id=org.id, - product=constants.PRDS['rhel'], - repo=constants.REPOS['rhel7_extra']['name'], - reposet=constants.REPOSET['rhel7_extra'], - releasever=None, - ) - rhel7_optional = target_sat.api_factory.enable_rhrepo_and_fetchid( - basearch='x86_64', - org_id=org.id, - product=constants.PRDS['rhel'], - repo=constants.REPOS['rhel7_optional']['name'], - reposet=constants.REPOSET['rhel7_optional'], - releasever=constants.REPOS['rhel7_optional']['releasever'], - ) - rhel7_sup = target_sat.api_factory.enable_rhrepo_and_fetchid( - basearch='x86_64', - org_id=org.id, - product=constants.PRDS['rhel'], - repo=constants.REPOS['rhel7_sup']['name'], - reposet=constants.REPOSET['rhel7_sup'], - releasever=constants.REPOS['rhel7_sup']['releasever'], - ) - rhel7_extra = target_sat.api.Repository(id=rhel7_extra).read() - rhel7_optional = target_sat.api.Repository(id=rhel7_optional).read() - rhel7_sup = target_sat.api.Repository(id=rhel7_sup).read() - for repo in [rhel7_extra, rhel7_optional, rhel7_sup]: - repo.sync(timeout=1200) - cv = target_sat.api.ContentView( - organization=org, + cv = module_target_sat.api.ContentView( + organization=module_sca_manifest_org, solve_dependencies=True, - repository=[rhel7_extra, rhel7_sup, rhel7_optional], + repository=module_big_repos, ).create() try: publish_task = cv.publish(synchronous=False) - target_sat.power_control(state='reboot', ensure=True) - target_sat.wait_for_tasks( + sleep_time = random.randint(0, 60) # publish takes ~70s in 6.15 and SatLab VM + sleep(sleep_time) + if reboot: + module_target_sat.power_control(state='reboot', ensure=True) + else: + module_target_sat.cli.Service.restart() + module_target_sat.wait_for_tasks( search_query=(f'id = {publish_task["id"]}'), search_rate=30, max_tries=60, ) except TaskFailedError: - target_sat.api.ForemanTask().bulk_resume(data={"task_ids": [publish_task['id']]}) - target_sat.wait_for_tasks( + module_target_sat.api.ForemanTask().bulk_resume(data={"task_ids": [publish_task['id']]}) + module_target_sat.wait_for_tasks( search_query=(f'id = {publish_task["id"]}'), search_rate=30, max_tries=60, ) - task_status = target_sat.api.ForemanTask(id=publish_task['id']).poll() - assert task_status['result'] == 'success' + task_status = module_target_sat.api.ForemanTask(id=publish_task['id']).poll() + assert ( + task_status['result'] == 'success' + ), f'Publish after restart failed, sleep_time was {sleep_time}' diff --git a/tests/foreman/destructive/test_discoveredhost.py b/tests/foreman/destructive/test_discoveredhost.py index bcf73a14ed7..8034957ec72 100644 --- a/tests/foreman/destructive/test_discoveredhost.py +++ b/tests/foreman/destructive/test_discoveredhost.py @@ -34,7 +34,7 @@ def _read_log(ch, pattern): def _wait_for_log(channel, pattern, timeout=5, delay=0.2): """_read_log method enclosed in wait_for method""" - matching_log = wait_for( + return wait_for( _read_log, func_args=( channel, @@ -45,7 +45,6 @@ def _wait_for_log(channel, pattern, timeout=5, delay=0.2): delay=delay, logger=logger, ) - return matching_log def _assert_discovered_host(host, channel=None, user_config=None, sat=None): diff --git a/tests/foreman/destructive/test_installer.py b/tests/foreman/destructive/test_installer.py index a020b66f96a..36776213c13 100644 --- a/tests/foreman/destructive/test_installer.py +++ b/tests/foreman/destructive/test_installer.py @@ -1,10 +1,10 @@ """Smoke tests to check installation health -:Requirement: Installer +:Requirement: Installation :CaseAutomation: Automated -:CaseComponent: Installer +:CaseComponent: Installation :Team: Platform diff --git a/tests/foreman/destructive/test_katello_certs_check.py b/tests/foreman/destructive/test_katello_certs_check.py index b1c7b4cb3eb..ce42047973a 100644 --- a/tests/foreman/destructive/test_katello_certs_check.py +++ b/tests/foreman/destructive/test_katello_certs_check.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: Certificates +:CaseComponent: Installation :Team: Platform @@ -15,8 +15,6 @@ import pytest -from robottelo.utils.issue_handlers import is_open - pytestmark = pytest.mark.destructive @@ -214,42 +212,31 @@ def test_positive_validate_capsule_certificate(capsule_certs_teardown): :CaseAutomation: Automated """ file_setup, target_sat = capsule_certs_teardown - DNS_Check = False + # extract the cert from the tar file result = target_sat.execute( f'tar -xf {file_setup["tmp_dir"]}/capsule_certs.tar ' f'--directory {file_setup["tmp_dir"]}/ ' ) assert result.status == 0, 'Extraction to working directory failed.' - # Extract raw data from RPM to a file - target_sat.execute( - 'rpm2cpio {0}/ssl-build/{1}/' - '{1}-qpid-router-server*.rpm' - '>> {0}/ssl-build/{1}/cert-raw-data'.format( - file_setup['tmp_dir'], file_setup['capsule_hostname'] - ) - ) + # Extract the cert data from file cert-raw-data and write to cert-data target_sat.execute( - 'openssl x509 -noout -text -in {0}/ssl-build/{1}/cert-raw-data' + 'openssl x509 -noout -text -in {0}/ssl-build/{1}/{1}-apache.crt' '>> {0}/ssl-build/{1}/cert-data'.format( file_setup['tmp_dir'], file_setup['capsule_hostname'] ) ) + # use same location on remote and local for cert_file target_sat.get(file_setup['caps_cert_file']) + # search the file for the line with DNS with open(file_setup['caps_cert_file']) as file: for line in file: if re.search(r'\bDNS:', line): match = re.search(r'{}'.format(file_setup['capsule_hostname']), line) assert match, 'No proxy name found.' - if is_open('BZ:1747581'): - DNS_Check = True - else: - match = re.search(r'\[]', line) - assert not match, 'Incorrect parsing of alternative proxy name.' - DNS_Check = True + match = re.search(r'\[]', line) + assert not match, 'Incorrect parsing of alternative proxy name.' break - # if no match for "DNS:" found, then raise error. - assert DNS_Check, 'Cannot find Subject Alternative Name' diff --git a/tests/foreman/destructive/test_ldap_authentication.py b/tests/foreman/destructive/test_ldap_authentication.py index 304c608734a..33539fff89e 100644 --- a/tests/foreman/destructive/test_ldap_authentication.py +++ b/tests/foreman/destructive/test_ldap_authentication.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: LDAP +:CaseComponent: Authentication :Team: Endeavour @@ -198,9 +198,8 @@ def test_positive_create_with_https( assert ldap_source['ldap_server']['port'] == '636' with module_target_sat.ui_session( test_name, username, auth_data['ldap_user_passwd'] - ) as ldapsession: - with pytest.raises(NavigationTriesExceeded): - ldapsession.user.search('') + ) as ldapsession, pytest.raises(NavigationTriesExceeded): + ldapsession.user.search('') assert module_target_sat.api.User().search(query={'search': f'login="{username}"'}) diff --git a/tests/foreman/destructive/test_ldapauthsource.py b/tests/foreman/destructive/test_ldapauthsource.py index 020aad9f716..0cebd782532 100644 --- a/tests/foreman/destructive/test_ldapauthsource.py +++ b/tests/foreman/destructive/test_ldapauthsource.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: LDAP +:CaseComponent: Authentication :Team: Endeavour diff --git a/tests/foreman/destructive/test_packages.py b/tests/foreman/destructive/test_packages.py index e81eb228268..da9c15f6515 100644 --- a/tests/foreman/destructive/test_packages.py +++ b/tests/foreman/destructive/test_packages.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: ForemanMaintain +:CaseComponent: SatelliteMaintain :Team: Platform diff --git a/tests/foreman/destructive/test_realm.py b/tests/foreman/destructive/test_realm.py index a4baa15e7bf..cbddcdc3bc8 100644 --- a/tests/foreman/destructive/test_realm.py +++ b/tests/foreman/destructive/test_realm.py @@ -87,7 +87,7 @@ def test_positive_realm_info_name( ) request.addfinalizer(lambda: module_target_sat.cli.Realm.delete({'id': realm['id']})) info = module_target_sat.cli.Realm.info({'name': realm['name']}) - for key in info.keys(): + for key in info: assert info[key] == realm[key] @@ -115,7 +115,7 @@ def test_positive_realm_info_id( ) request.addfinalizer(lambda: module_target_sat.cli.Realm.delete({'id': realm['id']})) info = module_target_sat.cli.Realm.info({'id': realm['id']}) - for key in info.keys(): + for key in info: assert info[key] == realm[key] assert info == module_target_sat.cli.Realm.info({'id': realm['id']}) diff --git a/tests/foreman/destructive/test_registration.py b/tests/foreman/destructive/test_registration.py index 357e779c5d7..9c8dbfa4929 100644 --- a/tests/foreman/destructive/test_registration.py +++ b/tests/foreman/destructive/test_registration.py @@ -14,6 +14,8 @@ from robottelo.config import settings +pytestmark = pytest.mark.destructive + @pytest.mark.tier3 @pytest.mark.no_containers diff --git a/tests/foreman/destructive/test_rename.py b/tests/foreman/destructive/test_rename.py index d5af1d6cfee..03fd9ff0d6f 100644 --- a/tests/foreman/destructive/test_rename.py +++ b/tests/foreman/destructive/test_rename.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: satellite-change-hostname +:CaseComponent: Installation :Team: Platform diff --git a/tests/foreman/endtoend/test_api_endtoend.py b/tests/foreman/endtoend/test_api_endtoend.py index e8bf062659c..cede80dfecb 100644 --- a/tests/foreman/endtoend/test_api_endtoend.py +++ b/tests/foreman/endtoend/test_api_endtoend.py @@ -1050,7 +1050,7 @@ def test_positive_find_admin_user(self): @pytest.mark.skipif( (not settings.robottelo.REPOS_HOSTING_URL), reason='Missing repos_hosting_url' ) - def test_positive_end_to_end(self, function_entitlement_manifest, target_sat, rhel_contenthost): + def test_positive_end_to_end(self, function_sca_manifest, target_sat, rhel_contenthost): """Perform end to end smoke tests using RH and custom repos. 1. Create a new user with admin permissions @@ -1091,10 +1091,9 @@ def test_positive_end_to_end(self, function_entitlement_manifest, target_sat, rh # step 2.1: Create a new organization user_cfg = user_nailgun_config(login, password) org = target_sat.api.Organization(server_config=user_cfg).create() - org.sca_disable() # step 2.2: Upload manifest - target_sat.upload_manifest(org.id, function_entitlement_manifest.content) + target_sat.upload_manifest(org.id, function_sca_manifest.content) # step 2.3: Create a new lifecycle environment le1 = target_sat.api.LifecycleEnvironment(server_config=user_cfg, organization=org).create() @@ -1152,12 +1151,7 @@ def test_positive_end_to_end(self, function_entitlement_manifest, target_sat, rh name=activation_key_name, environment=le1, organization=org, content_view=content_view ).create() - # step 2.13: Add the products to the activation key - for sub in target_sat.api.Subscription(organization=org).search(): - if sub.name == constants.DEFAULT_SUBSCRIPTION_NAME: - activation_key.add_subscriptions(data={'quantity': 1, 'subscription_id': sub.id}) - break - # step 2.13.1: Enable product content + # step 2.13: Enable product content activation_key.content_override( data={ 'content_overrides': [ diff --git a/tests/foreman/endtoend/test_cli_endtoend.py b/tests/foreman/endtoend/test_cli_endtoend.py index 30de18bd960..dc44d6691f7 100644 --- a/tests/foreman/endtoend/test_cli_endtoend.py +++ b/tests/foreman/endtoend/test_cli_endtoend.py @@ -70,7 +70,7 @@ def test_positive_cli_find_admin_user(module_target_sat): @pytest.mark.e2e @pytest.mark.upgrade @pytest.mark.skipif((not settings.robottelo.REPOS_HOSTING_URL), reason='Missing repos_hosting_url') -def test_positive_cli_end_to_end(function_entitlement_manifest, target_sat, rhel_contenthost): +def test_positive_cli_end_to_end(function_sca_manifest, target_sat, rhel_contenthost): """Perform end to end smoke tests using RH and custom repos. 1. Create a new user with admin permissions @@ -110,12 +110,11 @@ def test_positive_cli_end_to_end(function_entitlement_manifest, target_sat, rhel # step 2.1: Create a new organization org = _create(user, target_sat.cli.Org, {'name': gen_alphanumeric()}) - target_sat.cli.SimpleContentAccess.disable({'organization-id': org['id']}) # step 2.2: Clone and upload manifest - target_sat.put(f'{function_entitlement_manifest.path}', f'{function_entitlement_manifest.name}') + target_sat.put(f'{function_sca_manifest.path}', f'{function_sca_manifest.name}') target_sat.cli.Subscription.upload( - {'file': f'{function_entitlement_manifest.name}', 'organization-id': org['id']} + {'file': f'{function_sca_manifest.name}', 'organization-id': org['id']} ) # step 2.3: Create a new lifecycle environment @@ -226,20 +225,9 @@ def test_positive_cli_end_to_end(function_entitlement_manifest, target_sat, rhel ) # step 2.13: Add the products to the activation key - subscription_list = target_sat.cli.Subscription.with_user(user['login'], user['password']).list( + target_sat.cli.Subscription.with_user(user['login'], user['password']).list( {'organization-id': org['id']}, per_page=False ) - for subscription in subscription_list: - if subscription['name'] == constants.DEFAULT_SUBSCRIPTION_NAME: - target_sat.cli.ActivationKey.with_user( - user['login'], user['password'] - ).add_subscription( - { - 'id': activation_key['id'], - 'quantity': 1, - 'subscription-id': subscription['id'], - } - ) # step 2.13.1: Enable product content target_sat.cli.ActivationKey.with_user(user['login'], user['password']).content_override( @@ -266,14 +254,22 @@ def test_positive_cli_end_to_end(function_entitlement_manifest, target_sat, rhel ) content_host = target_sat.cli.Host.with_user(user['login'], user['password']).info( - {'id': content_host['id']} + {'id': content_host['id']}, output_format='json' ) + # check that content view matches what we passed - assert content_host['content-information']['content-view']['name'] == content_view['name'] + assert ( + content_host['content-information']['content-view-environments']['1']['content-view'][ + 'name' + ] + == content_view['name'] + ) # check that lifecycle environment matches assert ( - content_host['content-information']['lifecycle-environment']['name'] + content_host['content-information']['content-view-environments']['1'][ + 'lifecycle-environment' + ]['name'] == lifecycle_environment['name'] ) diff --git a/tests/foreman/installer/test_installer.py b/tests/foreman/installer/test_installer.py index 564614f44d5..696e05f52e2 100644 --- a/tests/foreman/installer/test_installer.py +++ b/tests/foreman/installer/test_installer.py @@ -1,10 +1,10 @@ """Smoke tests to check installation health -:Requirement: Installer +:Requirement: Installation :CaseAutomation: Automated -:CaseComponent: Installer +:CaseComponent: Installation :Team: Platform @@ -13,6 +13,7 @@ """ import pytest import requests +import yaml from robottelo import ssh from robottelo.config import settings @@ -21,1263 +22,6 @@ from robottelo.utils.installer import InstallerCommand from robottelo.utils.issue_handlers import is_open -PREVIOUS_INSTALLER_OPTIONS = { - '-', - '--[no-]colors', - '--[no-]enable-certs', - '--[no-]enable-apache-mod-status', - '--[no-]enable-foreman', - '--[no-]enable-foreman-cli', - '--[no-]enable-foreman-cli-ansible', - '--[no-]enable-foreman-cli-azure', - '--[no-]enable-foreman-cli-google', - '--[no-]enable-foreman-cli-katello', - '--[no-]enable-foreman-cli-kubevirt', - '--[no-]enable-foreman-cli-puppet', - '--[no-]enable-foreman-cli-remote-execution', - '--[no-]enable-foreman-cli-virt-who-configure', - '--[no-]enable-foreman-cli-webhooks', - '--[no-]enable-foreman-compute-ec2', - '--[no-]enable-foreman-compute-libvirt', - '--[no-]enable-foreman-compute-openstack', - '--[no-]enable-foreman-compute-ovirt', - '--[no-]enable-foreman-compute-vmware', - '--[no-]enable-foreman-plugin-ansible', - '--[no-]enable-foreman-plugin-azure', - '--[no-]enable-foreman-plugin-bootdisk', - '--[no-]enable-foreman-plugin-discovery', - '--[no-]enable-foreman-plugin-google', - '--[no-]enable-foreman-plugin-kubevirt', - '--[no-]enable-foreman-plugin-leapp', - '--[no-]enable-foreman-plugin-openscap', - '--[no-]enable-foreman-plugin-puppet', - '--[no-]enable-foreman-plugin-remote-execution', - '--[no-]enable-foreman-plugin-remote-execution-cockpit', - '--[no-]enable-foreman-plugin-rh-cloud', - '--[no-]enable-foreman-plugin-tasks', - '--[no-]enable-foreman-plugin-templates', - '--[no-]enable-foreman-plugin-virt-who-configure', - '--[no-]enable-foreman-plugin-webhooks', - '--[no-]enable-foreman-proxy', - '--[no-]enable-foreman-proxy-content', - '--[no-]enable-foreman-proxy-plugin-ansible', - '--[no-]enable-foreman-proxy-plugin-dhcp-infoblox', - '--[no-]enable-foreman-proxy-plugin-dhcp-remote-isc', - '--[no-]enable-foreman-proxy-plugin-discovery', - '--[no-]enable-foreman-proxy-plugin-dns-infoblox', - '--[no-]enable-foreman-proxy-plugin-openscap', - '--[no-]enable-foreman-proxy-plugin-remote-execution-script', - '--[no-]enable-foreman-proxy-plugin-shellhooks', - '--[no-]enable-katello', - '--[no-]enable-puppet', - '--[no-]lock-package-versions', - '--[no-]parser-cache', - '--[no-]verbose', - '--apache-mod-status-extended-status', - '--apache-mod-status-requires', - '--apache-mod-status-status-path', - '--certs-ca-common-name', - '--certs-ca-expiration', - '--certs-city', - '--certs-cname', - '--certs-country', - '--certs-default-ca-name', - '--certs-deploy', - '--certs-expiration', - '--certs-generate', - '--certs-group', - '--certs-node-fqdn', - '--certs-org', - '--certs-org-unit', - '--certs-pki-dir', - '--certs-regenerate', - '--certs-reset', - '--certs-server-ca-cert', - '--certs-server-ca-name', - '--certs-server-cert', - '--certs-server-cert-req', - '--certs-server-key', - '--certs-skip-check', - '--certs-ssl-build-dir', - '--certs-state', - '--certs-tar-file', - '--certs-update-all', - '--certs-update-server', - '--certs-update-server-ca', - '--certs-user', - '--color-of-background', - '--compare-scenarios', - '--detailed-exitcodes', - '--disable-scenario', - '--dont-save-answers', - '--enable-scenario', - '--force', - '--foreman-apache', - '--foreman-cli-foreman-url', - '--foreman-cli-manage-root-config', - '--foreman-cli-password', - '--foreman-cli-refresh-cache', - '--foreman-cli-request-timeout', - '--foreman-cli-ssl-ca-file', - '--foreman-cli-use-sessions', - '--foreman-cli-username', - '--foreman-cli-version', - '--foreman-client-ssl-ca', - '--foreman-client-ssl-cert', - '--foreman-client-ssl-key', - '--foreman-compute-ec2-version', - '--foreman-compute-libvirt-version', - '--foreman-compute-openstack-version', - '--foreman-compute-ovirt-version', - '--foreman-compute-vmware-version', - '--foreman-cors-domains', - '--foreman-db-database', - '--foreman-db-host', - '--foreman-db-manage', - '--foreman-db-manage-rake', - '--foreman-db-password', - '--foreman-db-pool', - '--foreman-db-port', - '--foreman-db-root-cert', - '--foreman-db-sslmode', - '--foreman-db-username', - '--foreman-dynflow-manage-services', - '--foreman-dynflow-orchestrator-ensure', - '--foreman-dynflow-redis-url', - '--foreman-dynflow-worker-concurrency', - '--foreman-dynflow-worker-instances', - '--foreman-email-delivery-method', - '--foreman-email-reply-address', - '--foreman-email-sendmail-arguments', - '--foreman-email-sendmail-location', - '--foreman-email-smtp-address', - '--foreman-email-smtp-authentication', - '--foreman-email-smtp-domain', - '--foreman-email-smtp-password', - '--foreman-email-smtp-port', - '--foreman-email-smtp-user-name', - '--foreman-email-subject-prefix', - '--foreman-foreman-service-puma-threads-max', - '--foreman-foreman-service-puma-threads-min', - '--foreman-foreman-service-puma-workers', - '--foreman-foreman-url', - '--foreman-gssapi-local-name', - '--foreman-hsts-enabled', - '--foreman-http-keytab', - '--foreman-initial-admin-email', - '--foreman-initial-admin-first-name', - '--foreman-initial-admin-last-name', - '--foreman-initial-admin-locale', - '--foreman-initial-admin-password', - '--foreman-initial-admin-timezone', - '--foreman-initial-admin-username', - '--foreman-initial-location', - '--foreman-initial-organization', - '--foreman-ipa-authentication', - '--foreman-ipa-authentication-api', - '--foreman-ipa-manage-sssd', - '--foreman-keycloak', - '--foreman-keycloak-app-name', - '--foreman-keycloak-realm', - '--foreman-loggers', - '--foreman-logging-layout', - '--foreman-logging-level', - '--foreman-logging-type', - '--foreman-oauth-active', - '--foreman-oauth-consumer-key', - '--foreman-oauth-consumer-secret', - '--foreman-oauth-effective-user', - '--foreman-oauth-map-users', - '--foreman-pam-service', - '--foreman-plugin-remote-execution-cockpit-ensure', - '--foreman-plugin-remote-execution-cockpit-origins', - '--foreman-plugin-tasks-automatic-cleanup', - '--foreman-plugin-tasks-backup', - '--foreman-plugin-tasks-cron-line', - '--foreman-plugin-version', - '--foreman-provisioning-ct-location', - '--foreman-provisioning-fcct-location', - '--foreman-proxy-autosignfile', - '--foreman-proxy-bind-host', - '--foreman-proxy-bmc', - '--foreman-proxy-bmc-default-provider', - '--foreman-proxy-bmc-listen-on', - '--foreman-proxy-bmc-redfish-verify-ssl', - '--foreman-proxy-bmc-ssh-key', - '--foreman-proxy-bmc-ssh-powercycle', - '--foreman-proxy-bmc-ssh-poweroff', - '--foreman-proxy-bmc-ssh-poweron', - '--foreman-proxy-bmc-ssh-powerstatus', - '--foreman-proxy-bmc-ssh-user', - '--foreman-proxy-content-enable-ansible', - '--foreman-proxy-content-enable-deb', - '--foreman-proxy-content-enable-docker', - '--foreman-proxy-content-enable-file', - '--foreman-proxy-content-enable-ostree', - '--foreman-proxy-content-enable-python', - '--foreman-proxy-content-enable-yum', - '--foreman-proxy-content-pulpcore-additional-export-paths', - '--foreman-proxy-content-pulpcore-additional-import-paths', - '--foreman-proxy-content-pulpcore-allowed-content-checksums', - '--foreman-proxy-content-pulpcore-api-service-worker-timeout', - '--foreman-proxy-content-pulpcore-cache-enabled', - '--foreman-proxy-content-pulpcore-cache-expires-ttl', - '--foreman-proxy-content-pulpcore-content-service-worker-timeout', - '--foreman-proxy-content-pulpcore-django-secret-key', - '--foreman-proxy-content-pulpcore-hide-guarded-distributions', - '--foreman-proxy-content-pulpcore-import-workers-percent', - '--foreman-proxy-content-pulpcore-manage-postgresql', - '--foreman-proxy-content-pulpcore-mirror', - '--foreman-proxy-content-pulpcore-postgresql-db-name', - '--foreman-proxy-content-pulpcore-postgresql-host', - '--foreman-proxy-content-pulpcore-postgresql-password', - '--foreman-proxy-content-pulpcore-postgresql-port', - '--foreman-proxy-content-pulpcore-postgresql-ssl', - '--foreman-proxy-content-pulpcore-postgresql-ssl-cert', - '--foreman-proxy-content-pulpcore-postgresql-ssl-key', - '--foreman-proxy-content-pulpcore-postgresql-ssl-require', - '--foreman-proxy-content-pulpcore-postgresql-ssl-root-ca', - '--foreman-proxy-content-pulpcore-postgresql-user', - '--foreman-proxy-content-pulpcore-telemetry', - '--foreman-proxy-content-pulpcore-worker-count', - '--foreman-proxy-content-reverse-proxy', - '--foreman-proxy-content-reverse-proxy-backend-protocol', - '--foreman-proxy-content-reverse-proxy-port', - '--foreman-proxy-dhcp', - '--foreman-proxy-dhcp-additional-interfaces', - '--foreman-proxy-dhcp-config', - '--foreman-proxy-dhcp-failover-address', - '--foreman-proxy-dhcp-failover-port', - '--foreman-proxy-dhcp-gateway', - '--foreman-proxy-dhcp-interface', - '--foreman-proxy-dhcp-ipxe-bootstrap', - '--foreman-proxy-dhcp-ipxefilename', - '--foreman-proxy-dhcp-key-name', - '--foreman-proxy-dhcp-key-secret', - '--foreman-proxy-dhcp-leases', - '--foreman-proxy-dhcp-listen-on', - '--foreman-proxy-dhcp-load-balance', - '--foreman-proxy-dhcp-load-split', - '--foreman-proxy-dhcp-manage-acls', - '--foreman-proxy-dhcp-managed', - '--foreman-proxy-dhcp-max-response-delay', - '--foreman-proxy-dhcp-max-unacked-updates', - '--foreman-proxy-dhcp-mclt', - '--foreman-proxy-dhcp-nameservers', - '--foreman-proxy-dhcp-netmask', - '--foreman-proxy-dhcp-network', - '--foreman-proxy-dhcp-node-type', - '--foreman-proxy-dhcp-omapi-port', - '--foreman-proxy-dhcp-option-domain', - '--foreman-proxy-dhcp-peer-address', - '--foreman-proxy-dhcp-ping-free-ip', - '--foreman-proxy-dhcp-provider', - '--foreman-proxy-dhcp-pxefilename', - '--foreman-proxy-dhcp-pxeserver', - '--foreman-proxy-dhcp-range', - '--foreman-proxy-dhcp-search-domains', - '--foreman-proxy-dhcp-server', - '--foreman-proxy-dhcp-subnets', - '--foreman-proxy-dns', - '--foreman-proxy-dns-forwarders', - '--foreman-proxy-dns-interface', - '--foreman-proxy-dns-listen-on', - '--foreman-proxy-dns-managed', - '--foreman-proxy-dns-provider', - '--foreman-proxy-dns-reverse', - '--foreman-proxy-dns-server', - '--foreman-proxy-dns-tsig-keytab', - '--foreman-proxy-dns-tsig-principal', - '--foreman-proxy-dns-ttl', - '--foreman-proxy-dns-zone', - '--foreman-proxy-ensure-packages-version', - '--foreman-proxy-foreman-base-url', - '--foreman-proxy-foreman-ssl-ca', - '--foreman-proxy-foreman-ssl-cert', - '--foreman-proxy-foreman-ssl-key', - '--foreman-proxy-freeipa-config', - '--foreman-proxy-freeipa-remove-dns', - '--foreman-proxy-groups', - '--foreman-proxy-http', - '--foreman-proxy-http-port', - '--foreman-proxy-httpboot', - '--foreman-proxy-httpboot-listen-on', - '--foreman-proxy-keyfile', - '--foreman-proxy-libvirt-connection', - '--foreman-proxy-libvirt-network', - '--foreman-proxy-log', - '--foreman-proxy-log-buffer', - '--foreman-proxy-log-buffer-errors', - '--foreman-proxy-log-level', - '--foreman-proxy-logs', - '--foreman-proxy-logs-listen-on', - '--foreman-proxy-manage-puppet-group', - '--foreman-proxy-manage-service', - '--foreman-proxy-oauth-consumer-key', - '--foreman-proxy-oauth-consumer-secret', - '--foreman-proxy-oauth-effective-user', - '--foreman-proxy-plugin-ansible-ansible-dir', - '--foreman-proxy-plugin-ansible-callback', - '--foreman-proxy-plugin-ansible-collections-paths', - '--foreman-proxy-plugin-ansible-enabled', - '--foreman-proxy-plugin-ansible-host-key-checking', - '--foreman-proxy-plugin-ansible-install-runner', - '--foreman-proxy-plugin-ansible-listen-on', - '--foreman-proxy-plugin-ansible-roles-path', - '--foreman-proxy-plugin-ansible-runner-package-name', - '--foreman-proxy-plugin-ansible-ssh-args', - '--foreman-proxy-plugin-ansible-working-dir', - '--foreman-proxy-plugin-dhcp-infoblox-dns-view', - '--foreman-proxy-plugin-dhcp-infoblox-network-view', - '--foreman-proxy-plugin-dhcp-infoblox-password', - '--foreman-proxy-plugin-dhcp-infoblox-record-type', - '--foreman-proxy-plugin-dhcp-infoblox-used-ips-search-type', - '--foreman-proxy-plugin-dhcp-infoblox-username', - '--foreman-proxy-plugin-dhcp-remote-isc-dhcp-config', - '--foreman-proxy-plugin-dhcp-remote-isc-dhcp-leases', - '--foreman-proxy-plugin-dhcp-remote-isc-key-name', - '--foreman-proxy-plugin-dhcp-remote-isc-key-secret', - '--foreman-proxy-plugin-dhcp-remote-isc-omapi-port', - '--foreman-proxy-plugin-discovery-enabled', - '--foreman-proxy-plugin-discovery-image-name', - '--foreman-proxy-plugin-discovery-install-images', - '--foreman-proxy-plugin-discovery-listen-on', - '--foreman-proxy-plugin-discovery-source-url', - '--foreman-proxy-plugin-discovery-tftp-root', - '--foreman-proxy-plugin-discovery-version', - '--foreman-proxy-plugin-dns-infoblox-dns-server', - '--foreman-proxy-plugin-dns-infoblox-dns-view', - '--foreman-proxy-plugin-dns-infoblox-password', - '--foreman-proxy-plugin-dns-infoblox-username', - '--foreman-proxy-plugin-openscap-ansible-module', - '--foreman-proxy-plugin-openscap-ansible-module-ensure', - '--foreman-proxy-plugin-openscap-contentdir', - '--foreman-proxy-plugin-openscap-corrupted-dir', - '--foreman-proxy-plugin-openscap-enabled', - '--foreman-proxy-plugin-openscap-failed-dir', - '--foreman-proxy-plugin-openscap-listen-on', - '--foreman-proxy-plugin-openscap-openscap-send-log-file', - '--foreman-proxy-plugin-openscap-proxy-name', - '--foreman-proxy-plugin-openscap-puppet-module', - '--foreman-proxy-plugin-openscap-puppet-module-ensure', - '--foreman-proxy-plugin-openscap-reportsdir', - '--foreman-proxy-plugin-openscap-spooldir', - '--foreman-proxy-plugin-openscap-timeout', - '--foreman-proxy-plugin-openscap-version', - '--foreman-proxy-plugin-remote-execution-script-cockpit-integration', - '--foreman-proxy-plugin-remote-execution-script-enabled', - '--foreman-proxy-plugin-remote-execution-script-generate-keys', - '--foreman-proxy-plugin-remote-execution-script-install-key', - '--foreman-proxy-plugin-remote-execution-script-listen-on', - '--foreman-proxy-plugin-remote-execution-script-local-working-dir', - '--foreman-proxy-plugin-remote-execution-script-mode', - '--foreman-proxy-plugin-remote-execution-script-mqtt-rate-limit', - '--foreman-proxy-plugin-remote-execution-script-mqtt-resend-interval', - '--foreman-proxy-plugin-remote-execution-script-mqtt-ttl', - '--foreman-proxy-plugin-remote-execution-script-remote-working-dir', - '--foreman-proxy-plugin-remote-execution-script-ssh-identity-dir', - '--foreman-proxy-plugin-remote-execution-script-ssh-identity-file', - '--foreman-proxy-plugin-remote-execution-script-ssh-kerberos-auth', - '--foreman-proxy-plugin-remote-execution-script-ssh-keygen', - '--foreman-proxy-plugin-remote-execution-script-ssh-log-level', - '--foreman-proxy-plugin-shellhooks-directory', - '--foreman-proxy-plugin-shellhooks-enabled', - '--foreman-proxy-plugin-shellhooks-listen-on', - '--foreman-proxy-plugin-shellhooks-version', - '--foreman-proxy-puppet', - '--foreman-proxy-puppet-api-timeout', - '--foreman-proxy-puppet-group', - '--foreman-proxy-puppet-listen-on', - '--foreman-proxy-puppet-ssl-ca', - '--foreman-proxy-puppet-ssl-cert', - '--foreman-proxy-puppet-ssl-key', - '--foreman-proxy-puppet-url', - '--foreman-proxy-puppetca', - '--foreman-proxy-puppetca-certificate', - '--foreman-proxy-puppetca-listen-on', - '--foreman-proxy-puppetca-provider', - '--foreman-proxy-puppetca-sign-all', - '--foreman-proxy-puppetca-token-ttl', - '--foreman-proxy-puppetca-tokens-file', - '--foreman-proxy-puppetdir', - '--foreman-proxy-realm', - '--foreman-proxy-realm-keytab', - '--foreman-proxy-realm-listen-on', - '--foreman-proxy-realm-principal', - '--foreman-proxy-realm-provider', - '--foreman-proxy-register-in-foreman', - '--foreman-proxy-registered-name', - '--foreman-proxy-registered-proxy-url', - '--foreman-proxy-registration', - '--foreman-proxy-registration-listen-on', - '--foreman-proxy-registration-url', - '--foreman-proxy-ssl', - '--foreman-proxy-ssl-ca', - '--foreman-proxy-ssl-cert', - '--foreman-proxy-ssl-disabled-ciphers', - '--foreman-proxy-ssl-key', - '--foreman-proxy-ssl-port', - '--foreman-proxy-ssldir', - '--foreman-proxy-template-url', - '--foreman-proxy-templates', - '--foreman-proxy-templates-listen-on', - '--foreman-proxy-tftp', - '--foreman-proxy-tftp-dirs', - '--foreman-proxy-tftp-listen-on', - '--foreman-proxy-tftp-manage-wget', - '--foreman-proxy-tftp-managed', - '--foreman-proxy-tftp-replace-grub2-cfg', - '--foreman-proxy-tftp-root', - '--foreman-proxy-tftp-servername', - '--foreman-proxy-tls-disabled-versions', - '--foreman-proxy-trusted-hosts', - '--foreman-proxy-version', - '--foreman-rails-cache-store', - '--foreman-register-in-foreman', - '--foreman-server-port', - '--foreman-server-ssl-ca', - '--foreman-server-ssl-cert', - '--foreman-server-ssl-chain', - '--foreman-server-ssl-crl', - '--foreman-server-ssl-key', - '--foreman-server-ssl-port', - '--foreman-server-ssl-protocol', - '--foreman-server-ssl-verify-client', - '--foreman-serveraliases', - '--foreman-servername', - '--foreman-ssl', - '--foreman-telemetry-logger-enabled', - '--foreman-telemetry-logger-level', - '--foreman-telemetry-prefix', - '--foreman-telemetry-prometheus-enabled', - '--foreman-telemetry-statsd-enabled', - '--foreman-telemetry-statsd-host', - '--foreman-telemetry-statsd-protocol', - '--foreman-trusted-proxies', - '--foreman-unattended', - '--foreman-unattended-url', - '--foreman-version', - '--foreman-websockets-encrypt', - '--foreman-websockets-ssl-cert', - '--foreman-websockets-ssl-key', - '--full-help', - '--help', - '--ignore-undocumented', - '--interactive', - '--katello-candlepin-db-host', - '--katello-candlepin-db-name', - '--katello-candlepin-db-password', - '--katello-candlepin-db-port', - '--katello-candlepin-db-ssl', - '--katello-candlepin-db-ssl-ca', - '--katello-candlepin-db-ssl-verify', - '--katello-candlepin-db-user', - '--katello-candlepin-loggers', - '--katello-candlepin-manage-db', - '--katello-candlepin-oauth-key', - '--katello-candlepin-oauth-secret', - '--katello-hosts-queue-workers', - '--katello-rest-client-timeout', - '--list-scenarios', - '--log-level', - '--migrations-only', - '--noop', - '--profile', - '--puppet-additional-settings', - '--puppet-agent', - '--puppet-agent-additional-settings', - '--puppet-agent-default-schedules', - '--puppet-agent-noop', - '--puppet-agent-restart-command', - '--puppet-agent-server-hostname', - '--puppet-agent-server-port', - '--puppet-allow-any-crl-auth', - '--puppet-auth-allowed', - '--puppet-autosign', - '--puppet-autosign-content', - '--puppet-autosign-entries', - '--puppet-autosign-mode', - '--puppet-autosign-source', - '--puppet-ca-crl-filepath', - '--puppet-ca-port', - '--puppet-ca-server', - '--puppet-certificate-revocation', - '--puppet-classfile', - '--puppet-client-certname', - '--puppet-client-package', - '--puppet-codedir', - '--puppet-cron-cmd', - '--puppet-dir', - '--puppet-dir-group', - '--puppet-dir-owner', - '--puppet-dns-alt-names', - '--puppet-environment', - '--puppet-group', - '--puppet-hiera-config', - '--puppet-http-connect-timeout', - '--puppet-http-read-timeout', - '--puppet-localconfig', - '--puppet-logdir', - '--puppet-manage-packages', - '--puppet-module-repository', - '--puppet-package-install-options', - '--puppet-package-provider', - '--puppet-package-source', - '--puppet-pluginfactsource', - '--puppet-pluginsource', - '--puppet-postrun-command', - '--puppet-prerun-command', - '--puppet-puppetconf-mode', - '--puppet-report', - '--puppet-run-hour', - '--puppet-run-minute', - '--puppet-rundir', - '--puppet-runinterval', - '--puppet-runmode', - '--puppet-server', - '--puppet-server-acceptor-threads', - '--puppet-server-additional-settings', - '--puppet-server-admin-api-allowlist', - '--puppet-server-allow-header-cert-info', - '--puppet-server-ca', - '--puppet-server-ca-allow-auth-extensions', - '--puppet-server-ca-allow-sans', - '--puppet-server-ca-auth-required', - '--puppet-server-ca-client-self-delete', - '--puppet-server-ca-client-allowlist', - '--puppet-server-ca-crl-sync', - '--puppet-server-ca-enable-infra-crl', - '--puppet-server-certname', - '--puppet-server-check-for-updates', - '--puppet-server-cipher-suites', - '--puppet-server-common-modules-path', - '--puppet-server-compile-mode', - '--puppet-server-connect-timeout', - '--puppet-server-crl-enable', - '--puppet-server-custom-trusted-oid-mapping', - '--puppet-server-default-manifest', - '--puppet-server-default-manifest-content', - '--puppet-server-default-manifest-path', - '--puppet-server-dir', - '--puppet-server-environment-class-cache-enabled', - '--puppet-server-environment-timeout', - '--puppet-server-environment-vars', - '--puppet-server-environments-group', - '--puppet-server-environments-mode', - '--puppet-server-environments-owner', - '--puppet-server-environments-recurse', - '--puppet-server-envs-dir', - '--puppet-server-envs-target', - '--puppet-server-external-nodes', - '--puppet-server-foreman', - '--puppet-server-foreman-facts', - '--puppet-server-foreman-ssl-ca', - '--puppet-server-foreman-ssl-cert', - '--puppet-server-foreman-ssl-key', - '--puppet-server-foreman-url', - '--puppet-server-git-branch-map', - '--puppet-server-git-repo', - '--puppet-server-git-repo-group', - '--puppet-server-git-repo-hook-mode', - '--puppet-server-git-repo-path', - '--puppet-server-git-repo-umask', - '--puppet-server-git-repo-user', - '--puppet-server-group', - '--puppet-server-http', - '--puppet-server-http-port', - '--puppet-server-idle-timeout', - '--puppet-server-ip', - '--puppet-server-jolokia-metrics-allowlist', - '--puppet-server-jruby-gem-home', - '--puppet-server-jvm-cli-args', - '--puppet-server-jvm-config', - '--puppet-server-jvm-extra-args', - '--puppet-server-jvm-java-bin', - '--puppet-server-jvm-max-heap-size', - '--puppet-server-jvm-min-heap-size', - '--puppet-server-manage-user', - '--puppet-server-max-active-instances', - '--puppet-server-max-open-files', - '--puppet-server-max-queued-requests', - '--puppet-server-max-requests-per-instance', - '--puppet-server-max-retry-delay', - '--puppet-server-max-threads', - '--puppet-server-metrics-allowed', - '--puppet-server-metrics-graphite-enable', - '--puppet-server-metrics-graphite-host', - '--puppet-server-metrics-graphite-interval', - '--puppet-server-metrics-graphite-port', - '--puppet-server-metrics-jmx-enable', - '--puppet-server-metrics-server-id', - '--puppet-server-multithreaded', - '--puppet-server-package', - '--puppet-server-parser', - '--puppet-server-port', - '--puppet-server-post-hook-content', - '--puppet-server-post-hook-name', - '--puppet-server-puppet-basedir', - '--puppet-server-puppetserver-auth-template', - '--puppet-server-puppetserver-dir', - '--puppet-server-puppetserver-experimental', - '--puppet-server-puppetserver-logdir', - '--puppet-server-puppetserver-metrics', - '--puppet-server-puppetserver-profiler', - '--puppet-server-puppetserver-rundir', - '--puppet-server-puppetserver-telemetry', - '--puppet-server-puppetserver-trusted-agents', - '--puppet-server-puppetserver-trusted-certificate-extensions', - '--puppet-server-puppetserver-vardir', - '--puppet-server-puppetserver-version', - '--puppet-server-reports', - '--puppet-server-request-timeout', - '--puppet-server-ruby-load-paths', - '--puppet-server-selector-threads', - '--puppet-server-ssl-acceptor-threads', - '--puppet-server-ssl-chain-filepath', - '--puppet-server-ssl-dir', - '--puppet-server-ssl-dir-manage', - '--puppet-server-ssl-key-manage', - '--puppet-server-ssl-protocols', - '--puppet-server-ssl-selector-threads', - '--puppet-server-storeconfigs', - '--puppet-server-strict-variables', - '--puppet-server-trusted-external-command', - '--puppet-server-user', - '--puppet-server-version', - '--puppet-server-versioned-code-content', - '--puppet-server-versioned-code-id', - '--puppet-server-web-idle-timeout', - '--puppet-service-name', - '--puppet-sharedir', - '--puppet-show-diff', - '--puppet-splay', - '--puppet-splaylimit', - '--puppet-srv-domain', - '--puppet-ssldir', - '--puppet-syslogfacility', - '--puppet-systemd-cmd', - '--puppet-systemd-randomizeddelaysec', - '--puppet-systemd-unit-name', - '--puppet-unavailable-runmodes', - '--puppet-use-srv-records', - '--puppet-usecacheonfailure', - '--puppet-user', - '--puppet-vardir', - '--puppet-version', - '--register-with-insights', - '--reset-apache-mod-status-extended-status', - '--reset-apache-mod-status-requires', - '--reset-apache-mod-status-status-path', - '--reset-certs-ca-common-name', - '--reset-certs-ca-expiration', - '--reset-certs-city', - '--reset-certs-cname', - '--reset-certs-country', - '--reset-certs-default-ca-name', - '--reset-certs-deploy', - '--reset-certs-expiration', - '--reset-certs-generate', - '--reset-certs-group', - '--reset-certs-node-fqdn', - '--reset-certs-org', - '--reset-certs-org-unit', - '--reset-certs-pki-dir', - '--reset-certs-regenerate', - '--reset-certs-server-ca-cert', - '--reset-certs-server-ca-name', - '--reset-certs-server-cert', - '--reset-certs-server-cert-req', - '--reset-certs-server-key', - '--reset-certs-ssl-build-dir', - '--reset-certs-state', - '--reset-certs-tar-file', - '--reset-certs-user', - '--reset-data', - '--reset-foreman-apache', - '--reset-foreman-cli-foreman-url', - '--reset-foreman-cli-manage-root-config', - '--reset-foreman-cli-password', - '--reset-foreman-cli-refresh-cache', - '--reset-foreman-cli-request-timeout', - '--reset-foreman-cli-ssl-ca-file', - '--reset-foreman-cli-use-sessions', - '--reset-foreman-cli-username', - '--reset-foreman-cli-version', - '--reset-foreman-client-ssl-ca', - '--reset-foreman-client-ssl-cert', - '--reset-foreman-client-ssl-key', - '--reset-foreman-compute-ec2-version', - '--reset-foreman-compute-libvirt-version', - '--reset-foreman-compute-openstack-version', - '--reset-foreman-compute-ovirt-version', - '--reset-foreman-compute-vmware-version', - '--reset-foreman-cors-domains', - '--reset-foreman-db-database', - '--reset-foreman-db-host', - '--reset-foreman-db-manage', - '--reset-foreman-db-manage-rake', - '--reset-foreman-db-password', - '--reset-foreman-db-pool', - '--reset-foreman-db-port', - '--reset-foreman-db-root-cert', - '--reset-foreman-db-sslmode', - '--reset-foreman-db-username', - '--reset-foreman-dynflow-manage-services', - '--reset-foreman-dynflow-orchestrator-ensure', - '--reset-foreman-dynflow-redis-url', - '--reset-foreman-dynflow-worker-concurrency', - '--reset-foreman-dynflow-worker-instances', - '--reset-foreman-email-delivery-method', - '--reset-foreman-email-reply-address', - '--reset-foreman-email-sendmail-arguments', - '--reset-foreman-email-sendmail-location', - '--reset-foreman-email-smtp-address', - '--reset-foreman-email-smtp-authentication', - '--reset-foreman-email-smtp-domain', - '--reset-foreman-email-smtp-password', - '--reset-foreman-email-smtp-port', - '--reset-foreman-email-smtp-user-name', - '--reset-foreman-email-subject-prefix', - '--reset-foreman-foreman-service-puma-threads-max', - '--reset-foreman-foreman-service-puma-threads-min', - '--reset-foreman-foreman-service-puma-workers', - '--reset-foreman-foreman-url', - '--reset-foreman-gssapi-local-name', - '--reset-foreman-hsts-enabled', - '--reset-foreman-http-keytab', - '--reset-foreman-initial-admin-email', - '--reset-foreman-initial-admin-first-name', - '--reset-foreman-initial-admin-last-name', - '--reset-foreman-initial-admin-locale', - '--reset-foreman-initial-admin-password', - '--reset-foreman-initial-admin-timezone', - '--reset-foreman-initial-admin-username', - '--reset-foreman-initial-location', - '--reset-foreman-initial-organization', - '--reset-foreman-ipa-authentication', - '--reset-foreman-ipa-authentication-api', - '--reset-foreman-ipa-manage-sssd', - '--reset-foreman-keycloak', - '--reset-foreman-keycloak-app-name', - '--reset-foreman-keycloak-realm', - '--reset-foreman-loggers', - '--reset-foreman-logging-layout', - '--reset-foreman-logging-level', - '--reset-foreman-logging-type', - '--reset-foreman-oauth-active', - '--reset-foreman-oauth-consumer-key', - '--reset-foreman-oauth-consumer-secret', - '--reset-foreman-oauth-effective-user', - '--reset-foreman-oauth-map-users', - '--reset-foreman-pam-service', - '--reset-foreman-plugin-remote-execution-cockpit-ensure', - '--reset-foreman-plugin-remote-execution-cockpit-origins', - '--reset-foreman-plugin-tasks-automatic-cleanup', - '--reset-foreman-plugin-tasks-backup', - '--reset-foreman-plugin-tasks-cron-line', - '--reset-foreman-plugin-version', - '--reset-foreman-provisioning-ct-location', - '--reset-foreman-provisioning-fcct-location', - '--reset-foreman-proxy-autosignfile', - '--reset-foreman-proxy-bind-host', - '--reset-foreman-proxy-bmc', - '--reset-foreman-proxy-bmc-default-provider', - '--reset-foreman-proxy-bmc-listen-on', - '--reset-foreman-proxy-bmc-redfish-verify-ssl', - '--reset-foreman-proxy-bmc-ssh-key', - '--reset-foreman-proxy-bmc-ssh-powercycle', - '--reset-foreman-proxy-bmc-ssh-poweroff', - '--reset-foreman-proxy-bmc-ssh-poweron', - '--reset-foreman-proxy-bmc-ssh-powerstatus', - '--reset-foreman-proxy-bmc-ssh-user', - '--reset-foreman-proxy-content-enable-ansible', - '--reset-foreman-proxy-content-enable-deb', - '--reset-foreman-proxy-content-enable-docker', - '--reset-foreman-proxy-content-enable-file', - '--reset-foreman-proxy-content-enable-ostree', - '--reset-foreman-proxy-content-enable-python', - '--reset-foreman-proxy-content-enable-yum', - '--reset-foreman-proxy-content-pulpcore-additional-export-paths', - '--reset-foreman-proxy-content-pulpcore-additional-import-paths', - '--reset-foreman-proxy-content-pulpcore-allowed-content-checksums', - '--reset-foreman-proxy-content-pulpcore-api-service-worker-timeout', - '--reset-foreman-proxy-content-pulpcore-cache-enabled', - '--reset-foreman-proxy-content-pulpcore-cache-expires-ttl', - '--reset-foreman-proxy-content-pulpcore-content-service-worker-timeout', - '--reset-foreman-proxy-content-pulpcore-django-secret-key', - '--reset-foreman-proxy-content-pulpcore-hide-guarded-distributions', - '--reset-foreman-proxy-content-pulpcore-import-workers-percent', - '--reset-foreman-proxy-content-pulpcore-manage-postgresql', - '--reset-foreman-proxy-content-pulpcore-mirror', - '--reset-foreman-proxy-content-pulpcore-postgresql-db-name', - '--reset-foreman-proxy-content-pulpcore-postgresql-host', - '--reset-foreman-proxy-content-pulpcore-postgresql-password', - '--reset-foreman-proxy-content-pulpcore-postgresql-port', - '--reset-foreman-proxy-content-pulpcore-postgresql-ssl', - '--reset-foreman-proxy-content-pulpcore-postgresql-ssl-cert', - '--reset-foreman-proxy-content-pulpcore-postgresql-ssl-key', - '--reset-foreman-proxy-content-pulpcore-postgresql-ssl-require', - '--reset-foreman-proxy-content-pulpcore-postgresql-ssl-root-ca', - '--reset-foreman-proxy-content-pulpcore-postgresql-user', - '--reset-foreman-proxy-content-pulpcore-telemetry', - '--reset-foreman-proxy-content-pulpcore-worker-count', - '--reset-foreman-proxy-content-reverse-proxy', - '--reset-foreman-proxy-content-reverse-proxy-backend-protocol', - '--reset-foreman-proxy-content-reverse-proxy-port', - '--reset-foreman-proxy-dhcp', - '--reset-foreman-proxy-dhcp-additional-interfaces', - '--reset-foreman-proxy-dhcp-config', - '--reset-foreman-proxy-dhcp-failover-address', - '--reset-foreman-proxy-dhcp-failover-port', - '--reset-foreman-proxy-dhcp-gateway', - '--reset-foreman-proxy-dhcp-interface', - '--reset-foreman-proxy-dhcp-ipxe-bootstrap', - '--reset-foreman-proxy-dhcp-ipxefilename', - '--reset-foreman-proxy-dhcp-key-name', - '--reset-foreman-proxy-dhcp-key-secret', - '--reset-foreman-proxy-dhcp-leases', - '--reset-foreman-proxy-dhcp-listen-on', - '--reset-foreman-proxy-dhcp-load-balance', - '--reset-foreman-proxy-dhcp-load-split', - '--reset-foreman-proxy-dhcp-manage-acls', - '--reset-foreman-proxy-dhcp-managed', - '--reset-foreman-proxy-dhcp-max-response-delay', - '--reset-foreman-proxy-dhcp-max-unacked-updates', - '--reset-foreman-proxy-dhcp-mclt', - '--reset-foreman-proxy-dhcp-nameservers', - '--reset-foreman-proxy-dhcp-netmask', - '--reset-foreman-proxy-dhcp-network', - '--reset-foreman-proxy-dhcp-node-type', - '--reset-foreman-proxy-dhcp-omapi-port', - '--reset-foreman-proxy-dhcp-option-domain', - '--reset-foreman-proxy-dhcp-peer-address', - '--reset-foreman-proxy-dhcp-ping-free-ip', - '--reset-foreman-proxy-dhcp-provider', - '--reset-foreman-proxy-dhcp-pxefilename', - '--reset-foreman-proxy-dhcp-pxeserver', - '--reset-foreman-proxy-dhcp-range', - '--reset-foreman-proxy-dhcp-search-domains', - '--reset-foreman-proxy-dhcp-server', - '--reset-foreman-proxy-dhcp-subnets', - '--reset-foreman-proxy-dns', - '--reset-foreman-proxy-dns-forwarders', - '--reset-foreman-proxy-dns-interface', - '--reset-foreman-proxy-dns-listen-on', - '--reset-foreman-proxy-dns-managed', - '--reset-foreman-proxy-dns-provider', - '--reset-foreman-proxy-dns-reverse', - '--reset-foreman-proxy-dns-server', - '--reset-foreman-proxy-dns-tsig-keytab', - '--reset-foreman-proxy-dns-tsig-principal', - '--reset-foreman-proxy-dns-ttl', - '--reset-foreman-proxy-dns-zone', - '--reset-foreman-proxy-ensure-packages-version', - '--reset-foreman-proxy-foreman-base-url', - '--reset-foreman-proxy-foreman-ssl-ca', - '--reset-foreman-proxy-foreman-ssl-cert', - '--reset-foreman-proxy-foreman-ssl-key', - '--reset-foreman-proxy-freeipa-config', - '--reset-foreman-proxy-freeipa-remove-dns', - '--reset-foreman-proxy-groups', - '--reset-foreman-proxy-http', - '--reset-foreman-proxy-http-port', - '--reset-foreman-proxy-httpboot', - '--reset-foreman-proxy-httpboot-listen-on', - '--reset-foreman-proxy-keyfile', - '--reset-foreman-proxy-libvirt-connection', - '--reset-foreman-proxy-libvirt-network', - '--reset-foreman-proxy-log', - '--reset-foreman-proxy-log-buffer', - '--reset-foreman-proxy-log-buffer-errors', - '--reset-foreman-proxy-log-level', - '--reset-foreman-proxy-logs', - '--reset-foreman-proxy-logs-listen-on', - '--reset-foreman-proxy-manage-puppet-group', - '--reset-foreman-proxy-manage-service', - '--reset-foreman-proxy-oauth-consumer-key', - '--reset-foreman-proxy-oauth-consumer-secret', - '--reset-foreman-proxy-oauth-effective-user', - '--reset-foreman-proxy-plugin-ansible-ansible-dir', - '--reset-foreman-proxy-plugin-ansible-callback', - '--reset-foreman-proxy-plugin-ansible-collections-paths', - '--reset-foreman-proxy-plugin-ansible-enabled', - '--reset-foreman-proxy-plugin-ansible-host-key-checking', - '--reset-foreman-proxy-plugin-ansible-install-runner', - '--reset-foreman-proxy-plugin-ansible-listen-on', - '--reset-foreman-proxy-plugin-ansible-roles-path', - '--reset-foreman-proxy-plugin-ansible-runner-package-name', - '--reset-foreman-proxy-plugin-ansible-ssh-args', - '--reset-foreman-proxy-plugin-ansible-working-dir', - '--reset-foreman-proxy-plugin-dhcp-infoblox-dns-view', - '--reset-foreman-proxy-plugin-dhcp-infoblox-network-view', - '--reset-foreman-proxy-plugin-dhcp-infoblox-password', - '--reset-foreman-proxy-plugin-dhcp-infoblox-record-type', - '--reset-foreman-proxy-plugin-dhcp-infoblox-used-ips-search-type', - '--reset-foreman-proxy-plugin-dhcp-infoblox-username', - '--reset-foreman-proxy-plugin-dhcp-remote-isc-dhcp-config', - '--reset-foreman-proxy-plugin-dhcp-remote-isc-dhcp-leases', - '--reset-foreman-proxy-plugin-dhcp-remote-isc-key-name', - '--reset-foreman-proxy-plugin-dhcp-remote-isc-key-secret', - '--reset-foreman-proxy-plugin-dhcp-remote-isc-omapi-port', - '--reset-foreman-proxy-plugin-discovery-enabled', - '--reset-foreman-proxy-plugin-discovery-image-name', - '--reset-foreman-proxy-plugin-discovery-install-images', - '--reset-foreman-proxy-plugin-discovery-listen-on', - '--reset-foreman-proxy-plugin-discovery-source-url', - '--reset-foreman-proxy-plugin-discovery-tftp-root', - '--reset-foreman-proxy-plugin-discovery-version', - '--reset-foreman-proxy-plugin-dns-infoblox-dns-server', - '--reset-foreman-proxy-plugin-dns-infoblox-dns-view', - '--reset-foreman-proxy-plugin-dns-infoblox-password', - '--reset-foreman-proxy-plugin-dns-infoblox-username', - '--reset-foreman-proxy-plugin-openscap-ansible-module', - '--reset-foreman-proxy-plugin-openscap-ansible-module-ensure', - '--reset-foreman-proxy-plugin-openscap-contentdir', - '--reset-foreman-proxy-plugin-openscap-corrupted-dir', - '--reset-foreman-proxy-plugin-openscap-enabled', - '--reset-foreman-proxy-plugin-openscap-failed-dir', - '--reset-foreman-proxy-plugin-openscap-listen-on', - '--reset-foreman-proxy-plugin-openscap-openscap-send-log-file', - '--reset-foreman-proxy-plugin-openscap-proxy-name', - '--reset-foreman-proxy-plugin-openscap-puppet-module', - '--reset-foreman-proxy-plugin-openscap-puppet-module-ensure', - '--reset-foreman-proxy-plugin-openscap-reportsdir', - '--reset-foreman-proxy-plugin-openscap-spooldir', - '--reset-foreman-proxy-plugin-openscap-timeout', - '--reset-foreman-proxy-plugin-openscap-version', - '--reset-foreman-proxy-plugin-remote-execution-script-cockpit-integration', - '--reset-foreman-proxy-plugin-remote-execution-script-enabled', - '--reset-foreman-proxy-plugin-remote-execution-script-generate-keys', - '--reset-foreman-proxy-plugin-remote-execution-script-install-key', - '--reset-foreman-proxy-plugin-remote-execution-script-listen-on', - '--reset-foreman-proxy-plugin-remote-execution-script-local-working-dir', - '--reset-foreman-proxy-plugin-remote-execution-script-mode', - '--reset-foreman-proxy-plugin-remote-execution-script-mqtt-rate-limit', - '--reset-foreman-proxy-plugin-remote-execution-script-mqtt-resend-interval', - '--reset-foreman-proxy-plugin-remote-execution-script-mqtt-ttl', - '--reset-foreman-proxy-plugin-remote-execution-script-remote-working-dir', - '--reset-foreman-proxy-plugin-remote-execution-script-ssh-identity-dir', - '--reset-foreman-proxy-plugin-remote-execution-script-ssh-identity-file', - '--reset-foreman-proxy-plugin-remote-execution-script-ssh-kerberos-auth', - '--reset-foreman-proxy-plugin-remote-execution-script-ssh-keygen', - '--reset-foreman-proxy-plugin-remote-execution-script-ssh-log-level', - '--reset-foreman-proxy-plugin-shellhooks-directory', - '--reset-foreman-proxy-plugin-shellhooks-enabled', - '--reset-foreman-proxy-plugin-shellhooks-listen-on', - '--reset-foreman-proxy-plugin-shellhooks-version', - '--reset-foreman-proxy-puppet', - '--reset-foreman-proxy-puppet-api-timeout', - '--reset-foreman-proxy-puppet-group', - '--reset-foreman-proxy-puppet-listen-on', - '--reset-foreman-proxy-puppet-ssl-ca', - '--reset-foreman-proxy-puppet-ssl-cert', - '--reset-foreman-proxy-puppet-ssl-key', - '--reset-foreman-proxy-puppet-url', - '--reset-foreman-proxy-puppetca', - '--reset-foreman-proxy-puppetca-certificate', - '--reset-foreman-proxy-puppetca-listen-on', - '--reset-foreman-proxy-puppetca-provider', - '--reset-foreman-proxy-puppetca-sign-all', - '--reset-foreman-proxy-puppetca-token-ttl', - '--reset-foreman-proxy-puppetca-tokens-file', - '--reset-foreman-proxy-puppetdir', - '--reset-foreman-proxy-realm', - '--reset-foreman-proxy-realm-keytab', - '--reset-foreman-proxy-realm-listen-on', - '--reset-foreman-proxy-realm-principal', - '--reset-foreman-proxy-realm-provider', - '--reset-foreman-proxy-register-in-foreman', - '--reset-foreman-proxy-registered-name', - '--reset-foreman-proxy-registered-proxy-url', - '--reset-foreman-proxy-registration', - '--reset-foreman-proxy-registration-listen-on', - '--reset-foreman-proxy-registration-url', - '--reset-foreman-proxy-ssl', - '--reset-foreman-proxy-ssl-ca', - '--reset-foreman-proxy-ssl-cert', - '--reset-foreman-proxy-ssl-disabled-ciphers', - '--reset-foreman-proxy-ssl-key', - '--reset-foreman-proxy-ssl-port', - '--reset-foreman-proxy-ssldir', - '--reset-foreman-proxy-template-url', - '--reset-foreman-proxy-templates', - '--reset-foreman-proxy-templates-listen-on', - '--reset-foreman-proxy-tftp', - '--reset-foreman-proxy-tftp-dirs', - '--reset-foreman-proxy-tftp-listen-on', - '--reset-foreman-proxy-tftp-manage-wget', - '--reset-foreman-proxy-tftp-managed', - '--reset-foreman-proxy-tftp-replace-grub2-cfg', - '--reset-foreman-proxy-tftp-root', - '--reset-foreman-proxy-tftp-servername', - '--reset-foreman-proxy-tls-disabled-versions', - '--reset-foreman-proxy-trusted-hosts', - '--reset-foreman-proxy-version', - '--reset-foreman-rails-cache-store', - '--reset-foreman-register-in-foreman', - '--reset-foreman-server-port', - '--reset-foreman-server-ssl-ca', - '--reset-foreman-server-ssl-cert', - '--reset-foreman-server-ssl-chain', - '--reset-foreman-server-ssl-crl', - '--reset-foreman-server-ssl-key', - '--reset-foreman-server-ssl-port', - '--reset-foreman-server-ssl-protocol', - '--reset-foreman-server-ssl-verify-client', - '--reset-foreman-serveraliases', - '--reset-foreman-servername', - '--reset-foreman-ssl', - '--reset-foreman-telemetry-logger-enabled', - '--reset-foreman-telemetry-logger-level', - '--reset-foreman-telemetry-prefix', - '--reset-foreman-telemetry-prometheus-enabled', - '--reset-foreman-telemetry-statsd-enabled', - '--reset-foreman-telemetry-statsd-host', - '--reset-foreman-telemetry-statsd-protocol', - '--reset-foreman-trusted-proxies', - '--reset-foreman-unattended', - '--reset-foreman-unattended-url', - '--reset-foreman-version', - '--reset-foreman-websockets-encrypt', - '--reset-foreman-websockets-ssl-cert', - '--reset-foreman-websockets-ssl-key', - '--reset-katello-candlepin-db-host', - '--reset-katello-candlepin-db-name', - '--reset-katello-candlepin-db-password', - '--reset-katello-candlepin-db-port', - '--reset-katello-candlepin-db-ssl', - '--reset-katello-candlepin-db-ssl-ca', - '--reset-katello-candlepin-db-ssl-verify', - '--reset-katello-candlepin-db-user', - '--reset-katello-candlepin-loggers', - '--reset-katello-candlepin-manage-db', - '--reset-katello-candlepin-oauth-key', - '--reset-katello-candlepin-oauth-secret', - '--reset-katello-hosts-queue-workers', - '--reset-katello-rest-client-timeout', - '--reset-puppet-additional-settings', - '--reset-puppet-agent', - '--reset-puppet-agent-additional-settings', - '--reset-puppet-agent-default-schedules', - '--reset-puppet-agent-noop', - '--reset-puppet-agent-restart-command', - '--reset-puppet-agent-server-hostname', - '--reset-puppet-agent-server-port', - '--reset-puppet-allow-any-crl-auth', - '--reset-puppet-auth-allowed', - '--reset-puppet-autosign', - '--reset-puppet-autosign-content', - '--reset-puppet-autosign-entries', - '--reset-puppet-autosign-mode', - '--reset-puppet-autosign-source', - '--reset-puppet-ca-crl-filepath', - '--reset-puppet-ca-port', - '--reset-puppet-ca-server', - '--reset-puppet-certificate-revocation', - '--reset-puppet-classfile', - '--reset-puppet-client-certname', - '--reset-puppet-client-package', - '--reset-puppet-codedir', - '--reset-puppet-cron-cmd', - '--reset-puppet-dir', - '--reset-puppet-dir-group', - '--reset-puppet-dir-owner', - '--reset-puppet-dns-alt-names', - '--reset-puppet-environment', - '--reset-puppet-group', - '--reset-puppet-hiera-config', - '--reset-puppet-http-connect-timeout', - '--reset-puppet-http-read-timeout', - '--reset-puppet-localconfig', - '--reset-puppet-logdir', - '--reset-puppet-manage-packages', - '--reset-puppet-module-repository', - '--reset-puppet-package-install-options', - '--reset-puppet-package-provider', - '--reset-puppet-package-source', - '--reset-puppet-pluginfactsource', - '--reset-puppet-pluginsource', - '--reset-puppet-postrun-command', - '--reset-puppet-prerun-command', - '--reset-puppet-puppetconf-mode', - '--reset-puppet-report', - '--reset-puppet-run-hour', - '--reset-puppet-run-minute', - '--reset-puppet-rundir', - '--reset-puppet-runinterval', - '--reset-puppet-runmode', - '--reset-puppet-server', - '--reset-puppet-server-acceptor-threads', - '--reset-puppet-server-additional-settings', - '--reset-puppet-server-admin-api-allowlist', - '--reset-puppet-server-allow-header-cert-info', - '--reset-puppet-server-ca', - '--reset-puppet-server-ca-allow-auth-extensions', - '--reset-puppet-server-ca-allow-sans', - '--reset-puppet-server-ca-auth-required', - '--reset-puppet-server-ca-client-self-delete', - '--reset-puppet-server-ca-client-allowlist', - '--reset-puppet-server-ca-crl-sync', - '--reset-puppet-server-ca-enable-infra-crl', - '--reset-puppet-server-certname', - '--reset-puppet-server-check-for-updates', - '--reset-puppet-server-cipher-suites', - '--reset-puppet-server-common-modules-path', - '--reset-puppet-server-compile-mode', - '--reset-puppet-server-connect-timeout', - '--reset-puppet-server-crl-enable', - '--reset-puppet-server-custom-trusted-oid-mapping', - '--reset-puppet-server-default-manifest', - '--reset-puppet-server-default-manifest-content', - '--reset-puppet-server-default-manifest-path', - '--reset-puppet-server-dir', - '--reset-puppet-server-environment-class-cache-enabled', - '--reset-puppet-server-environment-timeout', - '--reset-puppet-server-environment-vars', - '--reset-puppet-server-environments-group', - '--reset-puppet-server-environments-mode', - '--reset-puppet-server-environments-owner', - '--reset-puppet-server-environments-recurse', - '--reset-puppet-server-envs-dir', - '--reset-puppet-server-envs-target', - '--reset-puppet-server-external-nodes', - '--reset-puppet-server-foreman', - '--reset-puppet-server-foreman-facts', - '--reset-puppet-server-foreman-ssl-ca', - '--reset-puppet-server-foreman-ssl-cert', - '--reset-puppet-server-foreman-ssl-key', - '--reset-puppet-server-foreman-url', - '--reset-puppet-server-git-branch-map', - '--reset-puppet-server-git-repo', - '--reset-puppet-server-git-repo-group', - '--reset-puppet-server-git-repo-hook-mode', - '--reset-puppet-server-git-repo-path', - '--reset-puppet-server-git-repo-umask', - '--reset-puppet-server-git-repo-user', - '--reset-puppet-server-group', - '--reset-puppet-server-http', - '--reset-puppet-server-http-port', - '--reset-puppet-server-idle-timeout', - '--reset-puppet-server-ip', - '--reset-puppet-server-jolokia-metrics-allowlist', - '--reset-puppet-server-jruby-gem-home', - '--reset-puppet-server-jvm-cli-args', - '--reset-puppet-server-jvm-config', - '--reset-puppet-server-jvm-extra-args', - '--reset-puppet-server-jvm-java-bin', - '--reset-puppet-server-jvm-max-heap-size', - '--reset-puppet-server-jvm-min-heap-size', - '--reset-puppet-server-manage-user', - '--reset-puppet-server-max-active-instances', - '--reset-puppet-server-max-open-files', - '--reset-puppet-server-max-queued-requests', - '--reset-puppet-server-max-requests-per-instance', - '--reset-puppet-server-max-retry-delay', - '--reset-puppet-server-max-threads', - '--reset-puppet-server-metrics-allowed', - '--reset-puppet-server-metrics-graphite-enable', - '--reset-puppet-server-metrics-graphite-host', - '--reset-puppet-server-metrics-graphite-interval', - '--reset-puppet-server-metrics-graphite-port', - '--reset-puppet-server-metrics-jmx-enable', - '--reset-puppet-server-metrics-server-id', - '--reset-puppet-server-multithreaded', - '--reset-puppet-server-package', - '--reset-puppet-server-parser', - '--reset-puppet-server-port', - '--reset-puppet-server-post-hook-content', - '--reset-puppet-server-post-hook-name', - '--reset-puppet-server-puppet-basedir', - '--reset-puppet-server-puppetserver-auth-template', - '--reset-puppet-server-puppetserver-dir', - '--reset-puppet-server-puppetserver-experimental', - '--reset-puppet-server-puppetserver-logdir', - '--reset-puppet-server-puppetserver-metrics', - '--reset-puppet-server-puppetserver-profiler', - '--reset-puppet-server-puppetserver-rundir', - '--reset-puppet-server-puppetserver-telemetry', - '--reset-puppet-server-puppetserver-trusted-agents', - '--reset-puppet-server-puppetserver-trusted-certificate-extensions', - '--reset-puppet-server-puppetserver-vardir', - '--reset-puppet-server-puppetserver-version', - '--reset-puppet-server-reports', - '--reset-puppet-server-request-timeout', - '--reset-puppet-server-ruby-load-paths', - '--reset-puppet-server-selector-threads', - '--reset-puppet-server-ssl-acceptor-threads', - '--reset-puppet-server-ssl-chain-filepath', - '--reset-puppet-server-ssl-dir', - '--reset-puppet-server-ssl-dir-manage', - '--reset-puppet-server-ssl-key-manage', - '--reset-puppet-server-ssl-protocols', - '--reset-puppet-server-ssl-selector-threads', - '--reset-puppet-server-storeconfigs', - '--reset-puppet-server-strict-variables', - '--reset-puppet-server-trusted-external-command', - '--reset-puppet-server-user', - '--reset-puppet-server-version', - '--reset-puppet-server-versioned-code-content', - '--reset-puppet-server-versioned-code-id', - '--reset-puppet-server-web-idle-timeout', - '--reset-puppet-service-name', - '--reset-puppet-sharedir', - '--reset-puppet-show-diff', - '--reset-puppet-splay', - '--reset-puppet-splaylimit', - '--reset-puppet-srv-domain', - '--reset-puppet-ssldir', - '--reset-puppet-syslogfacility', - '--reset-puppet-systemd-cmd', - '--reset-puppet-systemd-randomizeddelaysec', - '--reset-puppet-systemd-unit-name', - '--reset-puppet-unavailable-runmodes', - '--reset-puppet-use-srv-records', - '--reset-puppet-usecacheonfailure', - '--reset-puppet-user', - '--reset-puppet-vardir', - '--reset-puppet-version', - '--scenario', - '--skip-checks-i-know-better', - '--skip-puppet-version-check', - '--tuning', - '--verbose-log-level', - '-S', - '-h', - '-i', - '-l', - '-n', - '-p', - '-s', - '-v', -} - -LAST_SAVED_SECTIONS = { - '= Generic:', - '= Module apache_mod_status:', - '= Module certs:', - '= Module foreman:', - '= Module foreman_cli:', - '= Module foreman_compute_ec2:', - '= Module foreman_compute_libvirt:', - '= Module foreman_compute_openstack:', - '= Module foreman_compute_ovirt:', - '= Module foreman_compute_vmware:', - '= Module foreman_plugin_remote_execution_cockpit:', - '= Module foreman_plugin_tasks:', - '= Module foreman_proxy:', - '= Module foreman_proxy_content:', - '= Module foreman_proxy_plugin_ansible:', - '= Module foreman_proxy_plugin_dhcp_infoblox:', - '= Module foreman_proxy_plugin_dhcp_remote_isc:', - '= Module foreman_proxy_plugin_discovery:', - '= Module foreman_proxy_plugin_dns_infoblox:', - '= Module foreman_proxy_plugin_openscap:', - '= Module foreman_proxy_plugin_shellhooks:', - '= Module foreman_proxy_plugin_remote_execution_script:', - '= Module katello:', - '= Module puppet:', -} - SATELLITE_SERVICES = [ 'dynflow-sidekiq@orchestrator', 'dynflow-sidekiq@worker-1', @@ -1292,6 +36,40 @@ 'tomcat', ] +UPSTREAM_SPECIFIC_MODULES = { + 'foreman::cli::discovery', + 'foreman::cli::openscap', + 'foreman::cli::rh_cloud', + 'foreman::cli::ssh', + 'foreman::cli::tasks', + 'foreman::cli::templates', + 'foreman::plugin::acd', + 'foreman::plugin::default_hostgroup', + 'foreman::plugin::dhcp_browser', + 'foreman::plugin::dlm', + 'foreman::plugin::expire_hosts', + 'foreman::plugin::git_templates', + 'foreman::plugin::hooks', + 'foreman::plugin::kernel_care', + 'foreman::plugin::monitoring', + 'foreman::plugin::netbox', + 'foreman::plugin::proxmox', + 'foreman::plugin::puppetdb', + 'foreman::plugin::rescue', + 'foreman::plugin::salt', + 'foreman::plugin::scc_manager', + 'foreman::plugin::setup', + 'foreman::plugin::snapshot_management', + 'foreman::plugin::statistics', + 'foreman::plugin::vault', + 'foreman::plugin::wreckingball', + 'foreman_proxy::plugin::acd', + 'foreman_proxy::plugin::dns::powerdns', + 'foreman_proxy::plugin::dns::route53', + 'foreman_proxy::plugin::monitoring', + 'foreman_proxy::plugin::salt', +} + def extract_help(filter='params'): """Generator function to extract satellite installer params and sections from lines @@ -1358,7 +136,7 @@ def install_satellite(satellite, installer_args, enable_fapolicyd=False): satellite.execute('dnf -y install fapolicyd && systemctl enable --now fapolicyd').status == 0 ) - satellite.execute('dnf -y module enable satellite:el8 && dnf -y install satellite') + satellite.install_satellite_or_capsule_package() if enable_fapolicyd: assert satellite.execute('rpm -q foreman-fapolicyd').status == 0 assert satellite.execute('rpm -q foreman-proxy-fapolicyd').status == 0 @@ -1381,8 +159,7 @@ def sat_default_install(module_sat_ready_rhels): f'foreman-initial-admin-password {settings.server.admin_password}', ] install_satellite(module_sat_ready_rhels[0], installer_args) - yield module_sat_ready_rhels[0] - common_sat_install_assertions(module_sat_ready_rhels[0]) + return module_sat_ready_rhels[0] @pytest.fixture(scope='module') @@ -1393,10 +170,14 @@ def sat_non_default_install(module_sat_ready_rhels): f'foreman-initial-admin-password {settings.server.admin_password}', 'foreman-rails-cache-store type:file', 'foreman-proxy-content-pulpcore-hide-guarded-distributions false', + 'enable-foreman-plugin-discovery', + 'foreman-proxy-plugin-discovery-install-images true', ] install_satellite(module_sat_ready_rhels[1], installer_args, enable_fapolicyd=True) - yield module_sat_ready_rhels[1] - common_sat_install_assertions(module_sat_ready_rhels[1]) + module_sat_ready_rhels[1].execute( + 'dnf -y --disableplugin=foreman-protector install foreman-discovery-image' + ) + return module_sat_ready_rhels[1] @pytest.mark.e2e @@ -1443,9 +224,7 @@ def test_capsule_installation(sat_non_default_install, cap_ready_rhel, setting_u ).status == 0 ) - cap_ready_rhel.execute( - 'dnf -y module enable satellite-capsule:el8 && dnf -y install satellite-capsule' - ) + cap_ready_rhel.install_satellite_or_capsule_package() assert cap_ready_rhel.execute('rpm -q foreman-proxy-fapolicyd').status == 0 # Setup Capsule setup_capsule(sat_non_default_install, cap_ready_rhel, org) @@ -1554,7 +333,7 @@ def test_content_guarded_distributions_option( )[0] rh_repo.sync() assert ( - "403: [('PEM routines', 'get_name', 'no start line')]" + "403" in sat_non_default_install.execute( f'curl https://{sat_non_default_install.hostname}/pulp/content/{org.label}' f'/Library/content/dist/layered/rhel8/x86_64/ansible/2.9/os/' @@ -1626,36 +405,29 @@ def test_positive_check_installer_hammer_ping(target_sat): assert 'ok' in line -@pytest.mark.e2e +@pytest.mark.stream @pytest.mark.upgrade @pytest.mark.tier3 -@pytest.mark.parametrize('filter', ['params', 'sections']) -def test_installer_options_and_sections(filter): - """Look for changes on installer sections and options/flags +@pytest.mark.build_sanity +def test_installer_modules_check(target_sat): + """Look for changes in installer modules :id: a51d3b9f-f347-4a96-a31a-770349db08c7 - :parametrized: yes - :steps: - 1. parse installer sections and options/flags - 2. compare with last saved data - - :expectedresults: Ideally sections and options should not change on zstreams. - Documentation must be updated accordingly when such changes occur. - So when this test fail we QE can act on it, asking dev if - changes occurs on zstream and checking docs are up to date. + 1. Parse installer modules for upstream and downstream - :CaseImportance: Medium + :expectedresults: Ensure the keys for all modules are in both files """ - current = set(extract_help(filter=filter)) - previous = PREVIOUS_INSTALLER_OPTIONS if filter == 'params' else LAST_SAVED_SECTIONS - removed = list(previous - current) - removed.sort() - added = list(current - previous) - added.sort() - msg = f"###Removed {filter}:\n{removed}\n###Added {filter}:\n{added}" - assert previous == current, msg + sat_output = target_sat.execute('cat /etc/foreman-installer/scenarios.d/satellite-answers.yaml') + upstream_output = target_sat.execute( + 'cat /etc/foreman-installer/scenarios.d/katello-answers.yaml' + ) + + sat_yaml = yaml.safe_load(sat_output.stdout) + upstream_yaml = yaml.safe_load(upstream_output.stdout) + + assert sat_yaml.keys() == (upstream_yaml.keys() - UPSTREAM_SPECIFIC_MODULES) @pytest.mark.stubbed @@ -1779,6 +551,37 @@ def test_installer_cap_pub_directory_accessibility(capsule_configured): assert 'Success!' in command_output.stdout +def test_installer_capsule_with_enabled_ansible(module_capsule_configured_ansible): + """Enables Ansible feature on external Capsule and checks the callback is set correctly + + :id: d60c475e-f4e7-11ee-af8a-98fa9b11ac24 + + :steps: + 1. Have a Satellite with external Capsule integrated + 2. Enable Ansible feature on external Capsule + 3. Check the ansible callback plugin on external Capsule + + :expectedresults: + Ansible callback plugin is overridden to "redhat.satellite.foreman" + + :CaseImportance: High + + :BZ: 2245081 + + :customerscenario: true + """ + ansible_env = '/etc/foreman-proxy/ansible.env' + downstream_callback = 'redhat.satellite.foreman' + callback_whitelist = module_capsule_configured_ansible.execute( + f"awk -F= '/ANSIBLE_CALLBACK_WHITELIST/{{print$2}}' {ansible_env}" + ) + assert callback_whitelist.stdout.strip('" \n') == downstream_callback + callbacks_enabled = module_capsule_configured_ansible.execute( + f"awk -F= '/ANSIBLE_CALLBACKS_ENABLED/{{print$2}}' {ansible_env}" + ) + assert callbacks_enabled.stdout.strip('" \n') == downstream_callback + + @pytest.mark.tier1 @pytest.mark.build_sanity @pytest.mark.first_sanity @@ -1810,3 +613,33 @@ def test_satellite_installation(installer_satellite): assert installer_satellite.execute('rpm -q foreman-redis').status == 0 settings_file = installer_satellite.load_remote_yaml_file(FOREMAN_SETTINGS_YML) assert settings_file.rails_cache_store.type == 'redis' + + +@pytest.mark.pit_server +@pytest.mark.parametrize('package', ['nmap-ncat']) +def test_weak_dependency(sat_non_default_install, package): + """Check if Satellite and its (sub)components do not require certain (potentially insecure) packages. On an existing Satellite the package has to be either not installed or can be safely removed. + + :id: c7988920-2f8c-4646-bde9-8823a3ca96bb + + :steps: + 1. Use satellite with non-default setup (for 'nmap-ncat' enable foreman discovery plugin and install foreman-discovery-image) + 2. Attempt to remove the package + + :expectedresults: + 1. The package can be either not installed or can be removed without removing any Satellite or Foreman packages + + :BZ: 1964539 + + :customerscenario: true + """ + result = sat_non_default_install.execute(f'dnf remove -y {package} --setopt tsflags=test') + + # no satellite or foreman package to be removed + assert 'satellite' not in result.stdout.lower() + assert 'foreman' not in result.stdout.lower() + # package not installed (nothing to remove) or safely removable + assert ( + 'No packages marked for removal.' in result.stderr + or 'Transaction test succeeded.' in result.stdout + ) diff --git a/tests/foreman/longrun/test_inc_updates.py b/tests/foreman/longrun/test_inc_updates.py index 7f916b03859..693f9441e45 100644 --- a/tests/foreman/longrun/test_inc_updates.py +++ b/tests/foreman/longrun/test_inc_updates.py @@ -22,9 +22,11 @@ ENVIRONMENT, FAKE_4_CUSTOM_PACKAGE, PRDS, + REAL_RHEL8_1_ERRATA_ID, REPOS, REPOSET, ) +from robottelo.logging import logger pytestmark = [pytest.mark.run_in_one_thread] @@ -50,10 +52,9 @@ def dev_lce(module_sca_manifest_org): @pytest.fixture(scope='module') def qe_lce(module_sca_manifest_org, dev_lce): - qe_lce = entities.LifecycleEnvironment( + return entities.LifecycleEnvironment( name='QE', prior=dev_lce, organization=module_sca_manifest_org ).create() - return qe_lce @pytest.fixture(scope='module') @@ -91,8 +92,7 @@ def module_cv(module_sca_manifest_org, sat_client_repo, custom_repo): repository=[sat_client_repo.id, custom_repo.id], ).create() module_cv.publish() - module_cv = module_cv.read() - return module_cv + return module_cv.read() @pytest.fixture(scope='module') @@ -214,3 +214,110 @@ def test_positive_noapply_api( outval['action'] == 'Incremental Update of 1 Content View Version(s) with 1 Package(s), and 1 Errata' ) + + +@pytest.mark.tier3 +def test_positive_incremental_update_time(module_target_sat, module_sca_manifest_org): + """Incremental update should not take a long time. + + :id: a9cdcc58-2d10-42cf-8e24-f7bec3b79d6b + + :steps: + 1. Setup larger rh repositories; rhel8 baseOS, rhst8, rhsc8. + 2. Create content view and add repos, sync and wait. + 3. Publish a content view version with all content. + 4. Using hammer, perform incremental update with errata, on that new version. + - Log the duration of the incremental update + 5. Publish the full content-view, with added incremental version. + - Log the duration of the content-view publish + + :expectedresults: + 1. Incremental update takes a short amount of time. + 2. Incremental update takes less time than full content-view publish, + or the time taken for both was close (within 20%). + + :BZ: 2117760, 1829266 + + :customerscenario: true + + """ + # create content view + cv = module_target_sat.cli_factory.make_content_view( + {'organization-id': module_sca_manifest_org.id} + ) + repo_sync_timestamp = ( + datetime.utcnow().replace(microsecond=0) - timedelta(seconds=1) + ).strftime('%Y-%m-%d %H:%M') + # setup rh repositories, add to cv, begin sync + for _repo in ['rhel8_bos', 'rhst8', 'rhsclient8']: + rh_repo_id = module_target_sat.api_factory.enable_rhrepo_and_fetchid( + basearch=DEFAULT_ARCHITECTURE, + org_id=module_sca_manifest_org.id, + product=PRDS['rhel8'], + repo=REPOS[_repo]['name'], + reposet=REPOSET[_repo], + releasever=REPOS[_repo]['releasever'], + ) + module_target_sat.cli.ContentView.add_repository( + { + 'id': cv['id'], + 'organization-id': module_sca_manifest_org.id, + 'repository-id': rh_repo_id, + } + ) + module_target_sat.api.Repository(id=rh_repo_id).sync(synchronous=False) + + # wait for all repo sync tasks + sync_tasks = module_target_sat.wait_for_tasks( + search_query=( + 'label = Actions::Katello::Repository::Sync' + f' and started_at >= "{repo_sync_timestamp}"' + ), + search_rate=10, + max_tries=200, + ) + assert all(task.poll()['result'] == 'success' for task in sync_tasks) + # publish and fetch new CVV + module_target_sat.cli.ContentView.publish({'id': cv['id']}) + content_view = module_target_sat.cli.ContentView.info({'id': cv['id']}) + cvv = content_view['versions'][0] + + # update incremental version via hammer, using one errata. + # expect: incr. "version-1.1" is created + update_start_time = datetime.utcnow() + result = module_target_sat.cli.ContentView.version_incremental_update( + {'content-view-version-id': cvv['id'], 'errata-ids': REAL_RHEL8_1_ERRATA_ID} + ) + assert 'version-1.1' in str(result[0].keys()) + update_duration = (datetime.utcnow() - update_start_time).total_seconds() + logger.info( + f'Update of incremental version-1.1, for CV id: {content_view["id"]},' + f' took {update_duration} seconds.' + ) + # publish the full CV, containing the added version-1.1 + publish_start_time = datetime.utcnow() + result = module_target_sat.cli.ContentView.publish({'id': cv['id']}) + publish_duration = (datetime.utcnow() - publish_start_time).total_seconds() + logger.info(f'Publish for CV id: {content_view["id"]}, took {publish_duration} seconds.') + # Per BZs: expect update duration was quicker than publish duration, + # if instead, update took longer, check that they were close, + # that update did not take ~significantly more time. + if update_duration >= publish_duration: + # unexpected: perhaps both tasks were very quick, took a handful of seconds, + # assert the difference was not significant (within 20%). + assert (update_duration - publish_duration) / publish_duration <= 0.2, ( + f'Incremental update took longer than publish of entire content-view id: {content_view["id"]}:' + f' Update took significantly more time, 20% or longer, than publish.' + f' update duration: {update_duration} s.\n publish duration: {publish_duration} s.' + ) + # else: base expected condition: update duration was quicker than publish. + + # some arbritrary timeouts, given amount of content in CV from repos. + assert update_duration <= 20, ( + 'Possible performance degradation in incremental update time.', + f' Took {update_duration} seconds, but expected to not exceed 20 seconds.', + ) + assert publish_duration <= 30, ( + 'Possible performance degradation in content-view publish time, after performing incremental update.', + f' Took {publish_duration} seconds, but expected to not exceed 30 seconds.', + ) diff --git a/tests/foreman/longrun/test_oscap.py b/tests/foreman/longrun/test_oscap.py index 045f18db639..53777de356b 100644 --- a/tests/foreman/longrun/test_oscap.py +++ b/tests/foreman/longrun/test_oscap.py @@ -70,10 +70,7 @@ def default_proxy(module_target_sat): @pytest.fixture(scope='module') def lifecycle_env(module_org): """Create lifecycle environment""" - lce_env = entities.LifecycleEnvironment( - organization=module_org, name=gen_string('alpha') - ).create() - return lce_env + return entities.LifecycleEnvironment(organization=module_org, name=gen_string('alpha')).create() @pytest.fixture(scope='module') diff --git a/tests/foreman/longrun/test_remoteexecution.py b/tests/foreman/longrun/test_remoteexecution.py new file mode 100644 index 00000000000..abff4966d7a --- /dev/null +++ b/tests/foreman/longrun/test_remoteexecution.py @@ -0,0 +1,50 @@ +"""Test module for Remote Execution + +:Requirement: Remoteexecution + +:CaseAutomation: Automated + +:CaseComponent: RemoteExecution + +:Team: Endeavour + +:CaseImportance: High + +""" +import pytest + + +@pytest.mark.rhel_ver_list([9]) +def test_positive_run_long_job(module_org, rex_contenthost, module_target_sat): + """Run a long running job + + :id: 76934868-89e6-4eb6-905e-d0d5ededc077 + + :expectedresults: Verify the long job was successfully ran and not terminated too soon + + :bz: 2270295 + + :parametrized: yes + """ + client = rex_contenthost + invocation_command = module_target_sat.cli_factory.job_invocation( + { + 'job-template': 'Run Command - Script Default', + 'inputs': 'command=sleep 700', + 'search-query': f"name ~ {client.hostname}", + }, + timeout='800s', + ) + result = module_target_sat.cli.JobInvocation.info({'id': invocation_command['id']}) + try: + assert result['success'] == '1' + except AssertionError as err: + raise AssertionError( + 'host output: {}'.format( + ' '.join( + module_target_sat.cli.JobInvocation.get_output( + {'id': invocation_command['id'], 'host': client.hostname} + ) + ) + ) + ) from err diff --git a/tests/foreman/maintain/test_advanced.py b/tests/foreman/maintain/test_advanced.py index 2771994d568..68b00e5c071 100644 --- a/tests/foreman/maintain/test_advanced.py +++ b/tests/foreman/maintain/test_advanced.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: ForemanMaintain +:CaseComponent: SatelliteMaintain :Team: Platform @@ -28,13 +28,12 @@ def get_satellite_capsule_repos( os_major_ver = get_sat_rhel_version().major if product == 'capsule': product = 'satellite-capsule' - repos = [ + return [ f'{product}-{x_y_release}-for-rhel-{os_major_ver}-x86_64-rpms', f'satellite-maintenance-{x_y_release}-for-rhel-{os_major_ver}-x86_64-rpms', f'rhel-{os_major_ver}-for-x86_64-baseos-rpms', f'rhel-{os_major_ver}-for-x86_64-appstream-rpms', ] - return repos def test_positive_advanced_run_service_restart(sat_maintain): @@ -76,6 +75,22 @@ def test_positive_advanced_run_hammer_setup(request, sat_maintain): :BZ: 1830355 """ + + @request.addfinalizer + def _finalize(): + result = sat_maintain.execute( + f'hammer -u admin -p admin user update --login admin --password {default_admin_pass}' + ) + assert result.status == 0 + # Make default admin creds available in MAINTAIN_HAMMER_YML + assert sat_maintain.cli.Advanced.run_hammer_setup().status == 0 + # Make sure default password available in MAINTAIN_HAMMER_YML + result = sat_maintain.execute( + f"grep -i ':password: {default_admin_pass}' {MAINTAIN_HAMMER_YML}" + ) + assert result.status == 0 + assert default_admin_pass in result.stdout + default_admin_pass = settings.server.admin_password result = sat_maintain.execute( f'hammer -u admin -p {default_admin_pass} user update --login admin --password admin' @@ -101,21 +116,6 @@ def test_positive_advanced_run_hammer_setup(request, sat_maintain): assert result.status == 0 assert 'admin' in result.stdout - @request.addfinalizer - def _finalize(): - result = sat_maintain.execute( - f'hammer -u admin -p admin user update --login admin --password {default_admin_pass}' - ) - assert result.status == 0 - # Make default admin creds available in MAINTAIN_HAMMER_YML - assert sat_maintain.cli.Advanced.run_hammer_setup().status == 0 - # Make sure default password available in MAINTAIN_HAMMER_YML - result = sat_maintain.execute( - f"grep -i ':password: {default_admin_pass}' {MAINTAIN_HAMMER_YML}" - ) - assert result.status == 0 - assert default_admin_pass in result.stdout - @pytest.mark.e2e @pytest.mark.upgrade @@ -132,6 +132,12 @@ def test_positive_advanced_run_packages(request, sat_maintain): :expectedresults: packages should install/downgrade/check-update/update. """ + + @request.addfinalizer + def _finalize(): + assert sat_maintain.execute('dnf remove -y walrus').status == 0 + sat_maintain.execute('rm -rf /etc/yum.repos.d/custom_repo.repo') + # Setup custom_repo and install walrus package sat_maintain.create_custom_repos(custom_repo=settings.repos.yum_0.url) result = sat_maintain.cli.Advanced.run_packages_install( @@ -161,11 +167,6 @@ def test_positive_advanced_run_packages(request, sat_maintain): assert result.status == 0 assert 'walrus-5.21-1' in result.stdout - @request.addfinalizer - def _finalize(): - assert sat_maintain.execute('dnf remove -y walrus').status == 0 - sat_maintain.execute('rm -rf /etc/yum.repos.d/custom_repo.repo') - @pytest.mark.parametrize( 'tasks_state', @@ -251,6 +252,7 @@ def test_positive_sync_plan_with_hammer_defaults(request, sat_maintain, module_o :customerscenario: true """ + sat_maintain.cli.Defaults.add({'param-name': 'organization_id', 'param-value': module_org.id}) sync_plans = [] @@ -259,16 +261,6 @@ def test_positive_sync_plan_with_hammer_defaults(request, sat_maintain, module_o sat_maintain.api.SyncPlan(enabled=True, name=name, organization=module_org).create() ) - result = sat_maintain.cli.Advanced.run_sync_plans_disable() - assert 'FAIL' not in result.stdout - assert result.status == 0 - - sync_plans[0].delete() - - result = sat_maintain.cli.Advanced.run_sync_plans_enable() - assert 'FAIL' not in result.stdout - assert result.status == 0 - @request.addfinalizer def _finalize(): sat_maintain.cli.Defaults.delete({'param-name': 'organization_id'}) @@ -279,6 +271,16 @@ def _finalize(): if sync_plan: sync_plans[0].delete() + result = sat_maintain.cli.Advanced.run_sync_plans_disable() + assert 'FAIL' not in result.stdout + assert result.status == 0 + + sync_plans[0].delete() + + result = sat_maintain.cli.Advanced.run_sync_plans_enable() + assert 'FAIL' not in result.stdout + assert result.status == 0 + @pytest.mark.e2e def test_positive_satellite_repositories_setup(sat_maintain): diff --git a/tests/foreman/maintain/test_backup_restore.py b/tests/foreman/maintain/test_backup_restore.py index a845e71a166..4abe8aa0391 100644 --- a/tests/foreman/maintain/test_backup_restore.py +++ b/tests/foreman/maintain/test_backup_restore.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: ForemanMaintain +:CaseComponent: SatelliteMaintain :Team: Platform @@ -326,39 +326,6 @@ def test_negative_backup_incremental_nodir(sat_maintain, setup_backup_tests, bac assert NOPREV_MSG in str(result.stderr) -@pytest.mark.include_capsule -def test_negative_backup_maintenance_mode(sat_maintain, setup_backup_tests): - """Try to take a backup which would fail and verify maintenance-mode isn't running/ON - - :id: dd53c19c-4ebf-11ed-a3d2-932dbdabb055 - - :parametrized: yes - - :steps: - 1. try to create a snapshot backup which would fail - - :expectedresults: - 1. Verify maintenance-mode isn't running if backup fails - - :BZ: 1908478, 1962842 - - :customerscenario: true - """ - subdir = f'{BACKUP_DIR}backup-{gen_string("alpha")}' - result = sat_maintain.cli.Backup.run_backup( - backup_dir=subdir, - backup_type='snapshot', - options={'assumeyes': True, 'plaintext': True}, - ) - assert result.status != 0 - assert "Backup didn't finish. Incomplete backup was removed." in result.stdout - - result = sat_maintain.cli.MaintenanceMode.status() - assert result.status == 0 - assert 'Status of maintenance-mode: Off' in result.stdout - assert 'Nftables table: absent' in result.stdout - - @pytest.mark.include_capsule def test_negative_restore_baddir_nodir(sat_maintain, setup_backup_tests): """Try to run restore with non-existing source dir provided diff --git a/tests/foreman/maintain/test_health.py b/tests/foreman/maintain/test_health.py index e927d85b5a5..9ba117f72cf 100644 --- a/tests/foreman/maintain/test_health.py +++ b/tests/foreman/maintain/test_health.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: ForemanMaintain +:CaseComponent: SatelliteMaintain :Team: Platform @@ -201,6 +201,13 @@ def test_negative_health_check_upstream_repository(sat_maintain, request): :expectedresults: check-upstream-repository health check should fail. """ + + @request.addfinalizer + def _finalize(): + for name in upstream_url: + sat_maintain.execute(f'rm -fr /etc/yum.repos.d/{name}.repo') + sat_maintain.execute('dnf clean all') + for name, url in upstream_url.items(): sat_maintain.create_custom_repos(**{name: url}) result = sat_maintain.cli.Health.check( @@ -216,12 +223,6 @@ def test_negative_health_check_upstream_repository(sat_maintain, request): elif name in ['foreman_repo', 'puppet_repo']: assert 'enabled=0' in result.stdout - @request.addfinalizer - def _finalize(): - for name in upstream_url: - sat_maintain.execute(f'rm -fr /etc/yum.repos.d/{name}.repo') - sat_maintain.execute('dnf clean all') - def test_positive_health_check_available_space(sat_maintain): """Verify available-space check @@ -260,15 +261,16 @@ def test_positive_hammer_defaults_set(sat_maintain, request): :customerscenario: true """ - sat_maintain.cli.Defaults.add({'param-name': 'organization_id', 'param-value': 1}) - result = sat_maintain.cli.Health.check(options={'assumeyes': True}) - assert result.status == 0 - assert 'FAIL' not in result.stdout @request.addfinalizer def _finalize(): sat_maintain.cli.Defaults.delete({'param-name': 'organization_id'}) + sat_maintain.cli.Defaults.add({'param-name': 'organization_id', 'param-value': 1}) + result = sat_maintain.cli.Health.check(options={'assumeyes': True}) + assert result.status == 0 + assert 'FAIL' not in result.stdout + @pytest.mark.include_capsule def test_positive_health_check_hotfix_installed(sat_maintain, request): @@ -288,6 +290,13 @@ def test_positive_health_check_hotfix_installed(sat_maintain, request): :expectedresults: check-hotfix-installed check should detect modified file and installed hotfix. """ + + @request.addfinalizer + def _finalize(): + sat_maintain.execute('rm -fr /etc/yum.repos.d/custom_repo.repo') + sat_maintain.execute('dnf remove -y hotfix-package') + assert sat_maintain.execute(f'sed -i "/#modifying_file/d" {fpath.stdout}').status == 0 + # Verify check-hotfix-installed without hotfix package. result = sat_maintain.cli.Health.check(options={'label': 'check-hotfix-installed'}) assert result.status == 0 @@ -307,12 +316,6 @@ def test_positive_health_check_hotfix_installed(sat_maintain, request): assert 'WARNING' in result.stdout assert 'hotfix-package' in result.stdout - @request.addfinalizer - def _finalize(): - sat_maintain.execute('rm -fr /etc/yum.repos.d/custom_repo.repo') - sat_maintain.execute('dnf remove -y hotfix-package') - assert sat_maintain.execute(f'sed -i "/#modifying_file/d" {fpath.stdout}').status == 0 - @pytest.mark.include_capsule def test_positive_health_check_validate_dnf_config(sat_maintain): @@ -365,6 +368,11 @@ def test_negative_health_check_epel_repository(request, sat_maintain): :expectedresults: check-non-redhat-repository health check should fail. """ + + @request.addfinalizer + def _finalize(): + assert sat_maintain.execute('dnf remove -y epel-release').status == 0 + epel_repo = 'https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm' sat_maintain.execute(f'dnf install -y {epel_repo}') result = sat_maintain.cli.Health.check(options={'label': 'check-non-redhat-repository'}) @@ -372,10 +380,6 @@ def test_negative_health_check_epel_repository(request, sat_maintain): assert result.status == 1 assert 'FAIL' in result.stdout - @request.addfinalizer - def _finalize(): - assert sat_maintain.execute('dnf remove -y epel-release').status == 0 - def test_positive_health_check_old_foreman_tasks(sat_maintain): """Verify check-old-foreman-tasks. @@ -471,6 +475,14 @@ def test_positive_health_check_tftp_storage(sat_maintain, request): :expectedresults: check-tftp-storage health check should pass. """ + + @request.addfinalizer + def _finalize(): + sat_maintain.cli.Settings.set({'name': 'token_duration', 'value': '360'}) + assert ( + sat_maintain.cli.Settings.list({'search': 'name=token_duration'})[0]['value'] == '360' + ) + sat_maintain.cli.Settings.set({'name': 'token_duration', 'value': '2'}) assert sat_maintain.cli.Settings.list({'search': 'name=token_duration'})[0]['value'] == '2' files_to_delete = [ @@ -504,13 +516,6 @@ def test_positive_health_check_tftp_storage(sat_maintain, request): assert result.status == 0 assert 'FAIL' not in result.stdout - @request.addfinalizer - def _finalize(): - sat_maintain.cli.Settings.set({'name': 'token_duration', 'value': '360'}) - assert ( - sat_maintain.cli.Settings.list({'search': 'name=token_duration'})[0]['value'] == '360' - ) - @pytest.mark.include_capsule def test_positive_health_check_env_proxy(sat_maintain): @@ -661,10 +666,20 @@ def test_positive_health_check_corrupted_roles(sat_maintain, request): :BZ: 1703041, 1908846 """ - # Check the filter created to verify the role, resource type, and permissions assigned. role_name = 'test_role' resource_type = gen_string("alpha") sat_maintain.cli.Role.create(options={'name': role_name}) + + @request.addfinalizer + def _finalize(): + resource_type = r"'\''Host'\''" + sat_maintain.execute( + f'''sudo su - postgres -c "psql -d foreman -c 'UPDATE permissions SET + resource_type = {resource_type} WHERE name = {permission_name};'"''' + ) + sat_maintain.cli.Role.delete(options={'name': role_name}) + + # Check the filter created to verify the role, resource type, and permissions assigned. sat_maintain.cli.Filter.create( options={'role': role_name, 'permissions': ['view_hosts', 'console_hosts']} ) @@ -686,15 +701,6 @@ def test_positive_health_check_corrupted_roles(sat_maintain, request): result = sat_maintain.cli.Filter.list(options={'search': role_name}, output_format='yaml') assert result.count('Id') == 4 - @request.addfinalizer - def _finalize(): - resource_type = r"'\''Host'\''" - sat_maintain.execute( - f'''sudo su - postgres -c "psql -d foreman -c 'UPDATE permissions SET - resource_type = {resource_type} WHERE name = {permission_name};'"''' - ) - sat_maintain.cli.Role.delete(options={'name': role_name}) - @pytest.mark.include_capsule def test_positive_health_check_non_rh_packages(sat_maintain, request): @@ -719,6 +725,12 @@ def test_positive_health_check_non_rh_packages(sat_maintain, request): :CaseImportance: High """ + + @request.addfinalizer + def _finalize(): + assert sat_maintain.execute('dnf remove -y walrus').status == 0 + assert sat_maintain.execute('rm -fr /etc/yum.repos.d/custom_repo.repo').status == 0 + sat_maintain.create_custom_repos(custom_repo=settings.repos.yum_0.url) assert ( sat_maintain.cli.Packages.install(packages='walrus', options={'assumeyes': True}).status @@ -730,11 +742,6 @@ def test_positive_health_check_non_rh_packages(sat_maintain, request): assert result.status == 78 assert 'WARNING' in result.stdout - @request.addfinalizer - def _finalize(): - assert sat_maintain.execute('dnf remove -y walrus').status == 0 - assert sat_maintain.execute('rm -fr /etc/yum.repos.d/custom_repo.repo').status == 0 - def test_positive_health_check_duplicate_permissions(sat_maintain): """Verify duplicate-permissions check diff --git a/tests/foreman/maintain/test_maintenance_mode.py b/tests/foreman/maintain/test_maintenance_mode.py index 3cc340f774b..b8d5024e1b1 100644 --- a/tests/foreman/maintain/test_maintenance_mode.py +++ b/tests/foreman/maintain/test_maintenance_mode.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: ForemanMaintain +:CaseComponent: SatelliteMaintain :Team: Platform @@ -47,6 +47,11 @@ def test_positive_maintenance_mode(request, sat_maintain, setup_sync_plan): to disable/enable sync-plan, stop/start crond.service and is able to add FOREMAN_MAINTAIN_TABLE rule in nftables. """ + + @request.addfinalizer + def _finalize(): + assert sat_maintain.cli.MaintenanceMode.stop().status == 0 + enable_sync_ids = setup_sync_plan data_yml_path = '/var/lib/foreman-maintain/data.yml' local_data_yml_path = f'{robottelo_tmp_dir}/data.yml' @@ -142,7 +147,3 @@ def test_positive_maintenance_mode(request, sat_maintain, setup_sync_plan): assert 'OK' in result.stdout assert result.status == 1 assert 'Maintenance mode is Off' in result.stdout - - @request.addfinalizer - def _finalize(): - assert sat_maintain.cli.MaintenanceMode.stop().status == 0 diff --git a/tests/foreman/maintain/test_offload_DB.py b/tests/foreman/maintain/test_offload_DB.py index ca0f64f87d8..0e1b3ced9d9 100644 --- a/tests/foreman/maintain/test_offload_DB.py +++ b/tests/foreman/maintain/test_offload_DB.py @@ -4,7 +4,7 @@ :CaseAutomation: ManualOnly -:CaseComponent: ForemanMaintain +:CaseComponent: SatelliteMaintain :Team: Platform @@ -28,5 +28,5 @@ def test_offload_internal_db_to_external_db_host(): :expectedresults: Installed successful, all services running - :CaseComponent: Installer + :CaseComponent: Installation """ diff --git a/tests/foreman/maintain/test_packages.py b/tests/foreman/maintain/test_packages.py index 9931d144352..f0ae22d6d10 100644 --- a/tests/foreman/maintain/test_packages.py +++ b/tests/foreman/maintain/test_packages.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: ForemanMaintain +:CaseComponent: SatelliteMaintain :Team: Platform @@ -160,6 +160,15 @@ def test_positive_fm_packages_install(request, sat_maintain): :expectedresults: Packages get install/update when lock/unlocked. """ + + @request.addfinalizer + def _finalize(): + assert sat_maintain.execute('dnf remove -y zsh').status == 0 + if sat_maintain.__class__.__name__ == 'Satellite': + result = sat_maintain.install(InstallerCommand('lock-package-versions')) + assert result.status == 0 + assert 'Success!' in result.stdout + # Test whether packages are locked or not result = sat_maintain.install(InstallerCommand('lock-package-versions')) assert result.status == 0 @@ -216,14 +225,6 @@ def test_positive_fm_packages_install(request, sat_maintain): assert result.status == 0 assert 'Use foreman-maintain packages install/update ' not in result.stdout - @request.addfinalizer - def _finalize(): - assert sat_maintain.execute('dnf remove -y zsh').status == 0 - if sat_maintain.__class__.__name__ == 'Satellite': - result = sat_maintain.install(InstallerCommand('lock-package-versions')) - assert result.status == 0 - assert 'Success!' in result.stdout - @pytest.mark.include_capsule def test_positive_fm_packages_update(request, sat_maintain): @@ -244,6 +245,12 @@ def test_positive_fm_packages_update(request, sat_maintain): :customerscenario: true """ + + @request.addfinalizer + def _finalize(): + assert sat_maintain.execute('dnf remove -y walrus').status == 0 + sat_maintain.execute('rm -rf /etc/yum.repos.d/custom_repo.repo') + # Setup custom_repo and packages update sat_maintain.create_custom_repos(custom_repo=settings.repos.yum_0.url) disableplugin = '--disableplugin=foreman-protector' @@ -263,8 +270,3 @@ def test_positive_fm_packages_update(request, sat_maintain): result = sat_maintain.execute('rpm -qa walrus') assert result.status == 0 assert 'walrus-5.21-1' in result.stdout - - @request.addfinalizer - def _finalize(): - assert sat_maintain.execute('dnf remove -y walrus').status == 0 - sat_maintain.execute('rm -rf /etc/yum.repos.d/custom_repo.repo') diff --git a/tests/foreman/maintain/test_service.py b/tests/foreman/maintain/test_service.py index 802ca684bb2..3ff33790055 100644 --- a/tests/foreman/maintain/test_service.py +++ b/tests/foreman/maintain/test_service.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: ForemanMaintain +:CaseComponent: SatelliteMaintain :Team: Platform @@ -225,7 +225,9 @@ def test_positive_foreman_service(sat_maintain): assert 'foreman' in result.stdout result = sat_maintain.cli.Service.status(options={'only': 'httpd'}) assert result.status == 0 - result = sat_maintain.cli.Health.check(options={'assumeyes': True}) + result = sat_maintain.cli.Health.check( + options={'assumeyes': True, 'whitelist': 'check-tftp-storage'} + ) assert result.status == 0 assert 'foreman' in result.stdout assert sat_maintain.cli.Service.start(options={'only': 'foreman'}).status == 0 diff --git a/tests/foreman/maintain/test_upgrade.py b/tests/foreman/maintain/test_upgrade.py index 13ea31a5de0..2a0f1a28ca8 100644 --- a/tests/foreman/maintain/test_upgrade.py +++ b/tests/foreman/maintain/test_upgrade.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: ForemanMaintain +:CaseComponent: SatelliteMaintain :Team: Platform @@ -98,8 +98,14 @@ def test_positive_repositories_validate(sat_maintain): @pytest.mark.parametrize( 'custom_host', [ - {'deploy_rhel_version': '8', 'deploy_flavor': 'satqe-ssd.disk.xxxl'}, - {'deploy_rhel_version': '8', 'deploy_flavor': 'satqe-ssd.standard.std'}, + { + 'deploy_rhel_version': settings.server.version.rhel_version, + 'deploy_flavor': 'satqe-ssd.disk.xxxl', + }, + { + 'deploy_rhel_version': settings.server.version.rhel_version, + 'deploy_flavor': 'satqe-ssd.standard.std', + }, ], ids=['default', 'medium'], indirect=True, @@ -118,15 +124,24 @@ def test_negative_pre_upgrade_tuning_profile_check(request, custom_host): :expectedresults: Pre-upgrade check fails. """ profile = request.node.callspec.id + rhel_major = custom_host.os_version.major sat_version = ".".join(settings.server.version.release.split('.')[0:2]) - # Register to CDN for RHEL8 repos, download and enable last y stream's ohsnap repos, + # Register to CDN for RHEL repos, download and enable last y stream's ohsnap repos, # and enable the satellite module and install it on the host custom_host.register_to_cdn() last_y_stream = last_y_stream_version( SATELLITE_VERSION if sat_version == 'stream' else sat_version ) - custom_host.download_repofile(product='satellite', release=last_y_stream) - custom_host.execute('dnf -y module enable satellite:el8 && dnf -y install satellite') + # Updated test to do a z-stream upgrade, as Satellite on RHEL9 is supported from 6.16 onwards. + # Remove this condition once 6.16 is GA + target_version = ( + '6.16.z' if (SATELLITE_VERSION == '6.16' and rhel_major == 9) else SATELLITE_VERSION + ) + if target_version == '6.16.z': + custom_host.download_repofile(product='satellite', release=sat_version) + else: + custom_host.download_repofile(product='satellite', release=last_y_stream) + custom_host.install_satellite_or_capsule_package() # Install with development tuning profile to get around installer checks custom_host.execute( 'satellite-installer --scenario satellite --tuning development', @@ -146,10 +161,10 @@ def test_negative_pre_upgrade_tuning_profile_check(request, custom_host): custom_host.execute('satellite-maintain upgrade list-versions') # Check that we can upgrade to the new Y stream version result = custom_host.execute('satellite-maintain upgrade list-versions') - assert SATELLITE_VERSION in result.stdout + assert target_version in result.stdout # Check that the upgrade check fails due to system requirements result = custom_host.execute( - f'satellite-maintain upgrade check --target-version {SATELLITE_VERSION}', timeout='5m' + f'satellite-maintain upgrade check --target-version {target_version}', timeout='5m' ) assert ( f'ERROR: The installer is configured to use the {profile} tuning ' diff --git a/tests/foreman/sys/test_dynflow.py b/tests/foreman/sys/test_dynflow.py index f08f07d4f8d..2b758bcb208 100644 --- a/tests/foreman/sys/test_dynflow.py +++ b/tests/foreman/sys/test_dynflow.py @@ -2,11 +2,11 @@ :CaseAutomation: Automated -:CaseComponent: Dynflow +:CaseComponent: TasksPlugin :Team: Endeavour -:Requirement: Dynflow +:Requirement: TasksPlugin :CaseImportance: High diff --git a/tests/foreman/sys/test_fam.py b/tests/foreman/sys/test_fam.py index 1a0d6a92b81..f4500a2b599 100644 --- a/tests/foreman/sys/test_fam.py +++ b/tests/foreman/sys/test_fam.py @@ -49,7 +49,7 @@ def setup_fam(module_target_sat, module_sca_manifest): module_target_sat.execute(f"sed -i '/^live/ s/$(MANIFEST)//' {FAM_ROOT_DIR}/Makefile") # Upload manifest to test playbooks directory - module_target_sat.put(module_sca_manifest.path, module_sca_manifest.name) + module_target_sat.put(str(module_sca_manifest.path), str(module_sca_manifest.name)) module_target_sat.execute( f'mv {module_sca_manifest.name} {FAM_ROOT_DIR}/tests/test_playbooks/data' ) diff --git a/tests/foreman/sys/test_katello_certs_check.py b/tests/foreman/sys/test_katello_certs_check.py index 5bfeed3eee5..820501f3713 100644 --- a/tests/foreman/sys/test_katello_certs_check.py +++ b/tests/foreman/sys/test_katello_certs_check.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: Certificates +:CaseComponent: Installation :Team: Platform @@ -44,10 +44,7 @@ def test_positive_install_sat_with_katello_certs(certs_data, sat_ready_rhel): ) sat_ready_rhel.register_to_cdn() sat_ready_rhel.execute('dnf -y update') - result = sat_ready_rhel.execute( - 'dnf -y module enable satellite:el8 && dnf -y install satellite' - ) - assert result.status == 0 + sat_ready_rhel.install_satellite_or_capsule_package() command = InstallerCommand( scenario='satellite', certs_server_cert=f'/root/{certs_data["cert_file_name"]}', diff --git a/tests/foreman/sys/test_webpack.py b/tests/foreman/sys/test_webpack.py new file mode 100644 index 00000000000..c71e9016e97 --- /dev/null +++ b/tests/foreman/sys/test_webpack.py @@ -0,0 +1,30 @@ +"""Test class for Webpack + +:CaseAutomation: Automated + +:CaseComponent: Installation + +:Requirement: Installation + +:Team: Endeavour + +:CaseImportance: High + +""" +import pytest + + +@pytest.mark.tier2 +def test_positive_webpack5(target_sat): + """Check whether Webpack 5 was used at packaging time + + :id: b7f3fbb2-ef4b-4634-877f-b8ea10373e04 + + :expectedresults: There is a file "public/webpack/foreman_tasks/foreman_tasks_remoteEntry.js" when Webpack 5 has been used. It used to be "public/webpack/foreman-tasks-.js" before. + """ + assert ( + target_sat.execute( + "find /usr/share/gems | grep public/webpack/foreman_tasks/foreman_tasks_remoteEntry.js" + ).status + == 0 + ) diff --git a/tests/foreman/ui/test_acs.py b/tests/foreman/ui/test_acs.py index 726a60c9ccb..40da1c22002 100644 --- a/tests/foreman/ui/test_acs.py +++ b/tests/foreman/ui/test_acs.py @@ -239,7 +239,7 @@ def gen_params(): # It loops through the keys in the parameters dictionary, and uses the keys to create a scenario ID # and then it uses the scenario ID to access the scenario values from the parameters dictionary. # The code then adds the scenario values to the list of scenario values. - for acs in parameters_dict.keys(): + for acs in parameters_dict: if not acs.startswith('_'): for cnt in parameters_dict[acs]: if not cnt.startswith('_'): diff --git a/tests/foreman/ui/test_ansible.py b/tests/foreman/ui/test_ansible.py index 2026ca8c095..c538ef099e1 100644 --- a/tests/foreman/ui/test_ansible.py +++ b/tests/foreman/ui/test_ansible.py @@ -1,337 +1,686 @@ -"""Test class for Ansible Roles and Variables pages +"""Test class for Ansible-ConfigurationManagement and Ansible-RemoteExecution components :Requirement: Ansible :CaseAutomation: Automated -:CaseComponent: Ansible - :Team: Rocket -:CaseImportance: High - +:CaseImportance: Critical """ from fauxfactory import gen_string import pytest +from wait_for import wait_for import yaml from robottelo import constants from robottelo.config import robottelo_tmp_dir, settings -def test_positive_create_and_delete_variable(target_sat): - """Create an Ansible variable with the minimum required values, then delete the variable. +class TestAnsibleCfgMgmt: + """Test class for Configuration Management with Ansible - :id: 7006d7c7-788a-4447-a564-d6b03ec06aaf + :CaseComponent: Ansible-ConfigurationManagement + """ - :steps: + @pytest.mark.tier2 + def test_positive_create_and_delete_variable(self, target_sat): + """Create an Ansible variable with the minimum required values, then delete the variable. + + :id: 7006d7c7-788a-4447-a564-d6b03ec06aaf + + :steps: + 1. Import Ansible roles if none have been imported yet. + 2. Create an Ansible variable with only a name and an assigned Ansible role. + 3. Verify that the Ansible variable has been created. + 4. Delete the Ansible variable. + 5. Verify that the Ansible Variable has been deleted. + + :expectedresults: The variable is successfully created and deleted. + """ + key = gen_string('alpha') + SELECTED_ROLE = 'redhat.satellite.activation_keys' + proxy_id = target_sat.nailgun_smart_proxy.id + target_sat.api.AnsibleRoles().sync( + data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]} + ) + with target_sat.ui_session() as session: + session.ansiblevariables.create( + { + 'key': key, + 'ansible_role': SELECTED_ROLE, + } + ) + assert session.ansiblevariables.search(key)[0]['Name'] == key + session.ansiblevariables.delete(key) + assert not session.ansiblevariables.search(key) + + @pytest.mark.tier3 + def test_positive_create_variable_with_overrides(self, target_sat): + """Create an Ansible variable with all values populated. + + :id: 90acea37-4c2f-42e5-92a6-0c88148f4fb6 + + :steps: + 1. Import Ansible roles if none have been imported yet. + 2. Create an Anible variable, populating all fields on the creation form. + 3. Verify that the Ansible variable was created successfully. + 4. Delete the Ansible variable. + 5. Verify that the Ansible Variable has been deleted. + + :expectedresults: The variable is successfully created. + """ + key = gen_string('alpha') + SELECTED_ROLE = 'redhat.satellite.activation_keys' + proxy_id = target_sat.nailgun_smart_proxy.id + target_sat.api.AnsibleRoles().sync( + data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]} + ) + with target_sat.ui_session() as session: + session.ansiblevariables.create_with_overrides( + { + 'key': key, + 'description': 'this is a description', + 'ansible_role': SELECTED_ROLE, + 'parameter_type': 'integer', + 'default_value': '11', + 'validator_type': 'list', + 'validator_rule': '11, 12, 13', + 'attribute_order': 'domain \n fqdn \n hostgroup \n os', + 'matcher_section.params': [ + { + 'attribute_type': {'matcher_key': 'os', 'matcher_value': 'fedora'}, + 'value': '13', + } + ], + } + ) + assert session.ansiblevariables.search(key)[0]['Name'] == key + session.ansiblevariables.delete(key) + assert not session.ansiblevariables.search(key) + + @pytest.mark.tier2 + def test_positive_host_role_information(self, target_sat, function_host): + """Assign Ansible Role to a Host and verify that the information + in the new UI is displayed correctly + + :id: 7da913ef-3b43-4bfa-9a45-d895431c8b56 + + :steps: + 1. Register a RHEL host to Satellite. + 2. Import all roles available by default. + 3. Assign one role to the RHEL host. + 4. Navigate to the new UI for the given Host. + 5. Select the 'Ansible' tab, then the 'Inventory' sub-tab. + + :expectedresults: Roles assigned directly to the Host are visible on the subtab. + """ + SELECTED_ROLE = 'RedHatInsights.insights-client' + + location = function_host.location.read() + organization = function_host.organization.read() + proxy_id = target_sat.nailgun_smart_proxy.id + target_sat.api.AnsibleRoles().sync( + data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]} + ) + target_sat.cli.Host.ansible_roles_assign( + {'id': function_host.id, 'ansible-roles': SELECTED_ROLE} + ) + host_roles = function_host.list_ansible_roles() + assert host_roles[0]['name'] == SELECTED_ROLE + with target_sat.ui_session() as session: + session.organization.select(organization.name) + session.location.select(location.name) + ansible_roles_table = session.host_new.get_ansible_roles(function_host.name) + assert ansible_roles_table[0]['Name'] == SELECTED_ROLE + all_assigned_roles_table = session.host_new.get_ansible_roles_modal(function_host.name) + assert all_assigned_roles_table[0]['Name'] == SELECTED_ROLE + + @pytest.mark.rhel_ver_match('8') + def test_positive_assign_ansible_role_variable_on_host( + self, + request, + target_sat, + rhel_contenthost, + module_activation_key, + module_org, + module_location, + ): + """Verify ansible variable is added to the role and attached to the host. + + :id: 7ec4fe19-5a08-4b10-bb4e-7327dd68699a + + :BZ: 2170727 + + :customerscenario: true + + :steps: + 1. Create an Ansible variable with array type and set the default value. + 2. Enable both 'Merge Overrides' and 'Merge Default'. + 3. Add the variable to a role and attach the role to the host. + 4. Verify that ansible role and variable is added to the host. + + :expectedresults: The role and variable is successfully added to the host. + """ + + @request.addfinalizer + def _finalize(): + result = target_sat.cli.Ansible.roles_delete({'name': SELECTED_ROLE}) + assert f'Ansible role [{SELECTED_ROLE}] was deleted.' in result[0]['message'] + + key = gen_string('alpha') + SELECTED_ROLE = 'redhat.satellite.activation_keys' + proxy_id = target_sat.nailgun_smart_proxy.id + target_sat.api.AnsibleRoles().sync( + data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]} + ) + command = target_sat.api.RegistrationCommand( + organization=module_org, + location=module_location, + activation_keys=[module_activation_key.name], + ).create() + result = rhel_contenthost.execute(command) + assert result.status == 0, f'Failed to register host: {result.stderr}' + target_host = rhel_contenthost.nailgun_host + default_value = '[\"test\"]' + parameter_type = 'array' + with target_sat.ui_session() as session: + session.organization.select(org_name=module_org.name) + session.location.select(loc_name=module_location.name) + session.ansiblevariables.create_with_overrides( + { + 'key': key, + 'ansible_role': SELECTED_ROLE, + 'override': 'true', + 'parameter_type': parameter_type, + 'default_value': default_value, + 'validator_type': None, + 'attribute_order': 'domain \n fqdn \n hostgroup \n os', + 'merge_default': 'true', + 'merge_overrides': 'true', + 'matcher_section.params': [ + { + 'attribute_type': {'matcher_key': 'os', 'matcher_value': 'fedora'}, + 'value': '[\'13\']', + } + ], + } + ) + result = target_sat.cli.Host.ansible_roles_assign( + {'id': target_host.id, 'ansible-roles': SELECTED_ROLE} + ) + assert 'Ansible roles were assigned' in result[0]['message'] + values = session.host_new.get_details(rhel_contenthost.hostname, 'ansible')['ansible'][ + 'variables' + ]['table'] + assert (key, SELECTED_ROLE, default_value, parameter_type) in [ + (var['Name'], var['Ansible role'], var['Value'], var['Type']) for var in values + ] + + @pytest.mark.stubbed + @pytest.mark.tier2 + def test_positive_role_variable_information(self): + """Create and assign variables to an Ansible Role and verify that the information in + the new UI is displayed correctly + + :id: 4ab2813a-6b83-4907-b104-0473465814f5 + + :steps: + 1. Register a RHEL host to Satellite. + 2. Import all roles available by default. + 3. Create a host group and assign one of the Ansible roles to the host group. + 4. Assign the host to the host group. + 5. Assign one roles to the RHEL host. + 6. Create a variable and associate it with the role assigned to the Host. + 7. Create a variable and associate it with the role assigned to the Hostgroup. + 8. Navigate to the new UI for the given Host. + 9. Select the 'Ansible' tab, then the 'Variables' sub-tab. + + :expectedresults: The variables information for the given Host is visible. + """ + + @pytest.mark.stubbed + @pytest.mark.tier2 + def test_positive_assign_role_in_new_ui(self): + """Using the new Host UI, assign a role to a Host + + :id: 044f38b4-cff2-4ddc-b93c-7e9f2826d00d + + :steps: + 1. Register a RHEL host to Satellite. + 2. Import all roles available by default. + 3. Navigate to the new UI for the given Host. + 4. Select the 'Ansible' tab + 5. Click the 'Assign Ansible Roles' button. + 6. Using the popup, assign a role to the Host. + + :expectedresults: The Role is successfully assigned to the Host, and visible on the UI + """ + + @pytest.mark.stubbed + @pytest.mark.tier2 + def test_positive_remove_role_in_new_ui(self): + """Using the new Host UI, remove the role(s) of a Host + + :id: d6de5130-45f6-4349-b490-fbde2aed082c + + :steps: + 1. Register a RHEL host to Satellite. + 2. Import all roles available by default. + 3. Assign a role to the host. + 4. Navigate to the new UI for the given Host. + 5. Select the 'Ansible' tab + 6. Click the 'Edit Ansible roles' button. + 7. Using the popup, remove the assigned role from the Host. + + :expectedresults: Role is successfully removed from the Host, and not visible on the UI + """ + + @pytest.mark.stubbed + @pytest.mark.tier3 + def test_positive_ansible_config_report_failed_tasks_errors(self): + """Check that failed Ansible tasks show as errors in the config report + + :id: 1a91e534-143f-4f35-953a-7ad8b7d2ddf3 + + :steps: + 1. Import Ansible roles + 2. Assign Ansible roles to a host + 3. Run Ansible roles on host + + :expectedresults: Verify that any task failures are listed as errors in the config report + + :CaseAutomation: NotAutomated + """ + + @pytest.mark.stubbed + @pytest.mark.tier3 + def test_positive_ansible_config_report_changes_notice(self): + """Check that Ansible tasks that make changes on a host show as notice in the config report + + :id: 8c90f179-8b70-4932-a477-75dc3566c437 + + :steps: + 1. Import Ansible Roles + 2. Assign Ansible roles to a host + 3. Run Ansible Roles on a host + + :expectedresults: Verify that any tasks that make changes on the host + are listed as notice in the config report - 1. Import Ansible roles if none have been imported yet. - 2. Create an Ansible variable with only a name and an assigned Ansible role. - 3. Verify that the Ansible variable has been created. - 4. Delete the Ansible variable. - 5. Verify that the Ansible Variable has been deleted. + :CaseAutomation: NotAutomated + """ - :expectedresults: The variable is successfully created and deleted. - """ - key = gen_string('alpha') - - SELECTED_ROLE = 'redhat.satellite.activation_keys' - proxy_id = target_sat.nailgun_smart_proxy.id - target_sat.api.AnsibleRoles().sync(data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]}) - with target_sat.ui_session() as session: - session.ansiblevariables.create( - { - 'key': key, - 'ansible_role': SELECTED_ROLE, - } - ) - assert session.ansiblevariables.search(key)[0]['Name'] == key - session.ansiblevariables.delete(key) - assert not session.ansiblevariables.search(key) + @pytest.mark.stubbed + @pytest.mark.tier3 + def test_positive_ansible_variables_imported_with_roles(self): + """Verify that, when Ansible roles are imported, their variables are imported simultaneously + :id: 107c53e8-5a8a-4291-bbde-fbd66a0bb85e -def test_positive_create_variable_with_overrides(target_sat): - """Create an Ansible variable with all values populated. + :steps: + 1. Import Ansible roles + 2. Navigate to Configure > Variables - :id: 90acea37-4c2f-42e5-92a6-0c88148f4fb6 + :expectedresults: Verify that any variables in the role were also imported to Satellite - :steps: + :CaseAutomation: NotAutomated + """ - 1. Import Ansible roles if none have been imported yet. - 2. Create an Anible variable, populating all fields on the creation form. - 3. Verify that the Ansible variable was created successfully. + @pytest.mark.stubbed + @pytest.mark.tier3 + def test_positive_ansible_roles_ignore_list(self): + """Verify that the ignore list setting prevents selected roles from being available for import - :expectedresults: The variable is successfully created. - """ - key = gen_string('alpha') - - SELECTED_ROLE = 'redhat.satellite.activation_keys' - proxy_id = target_sat.nailgun_smart_proxy.id - target_sat.api.AnsibleRoles().sync(data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]}) - with target_sat.ui_session() as session: - session.ansiblevariables.create_with_overrides( - { - 'key': key, - 'description': 'this is a description', - 'ansible_role': SELECTED_ROLE, - 'parameter_type': 'integer', - 'default_value': '11', - 'validator_type': 'list', - 'validator_rule': '11, 12, 13', - 'attribute_order': 'domain \n fqdn \n hostgroup \n os', - 'matcher_section.params': [ - { - 'attribute_type': {'matcher_key': 'os', 'matcher_value': 'fedora'}, - 'value': '13', - } - ], - } - ) - assert session.ansiblevariables.search(key)[0]['Name'] == key + :id: 6fa1d8f0-b583-4a07-88eb-c9ae7fcd0219 + :steps: + 1. Add roles to the ignore list in Administer > Settings > Ansible + 2. Navigate to Configure > Roles -@pytest.mark.pit_server -@pytest.mark.no_containers -@pytest.mark.rhel_ver_match('[^6]') -def test_positive_config_report_ansible(session, target_sat, module_org, rhel_contenthost): - """Test Config Report generation with Ansible Jobs. + :expectedresults: Verify that any roles on the ignore list are not available for import - :id: 118e25e5-409e-44ba-b908-217da9722576 + :CaseAutomation: NotAutomated + """ - :steps: - 1. Register a content host with satellite - 2. Import a role into satellite - 3. Assign that role to a host - 4. Assert that the role was assigned to the host successfully - 5. Run the Ansible playbook associated with that role - 6. Check if the report is created successfully + @pytest.mark.stubbed + @pytest.mark.tier3 + def test_positive_ansible_variables_installed_with_collection(self): + """Verify that installing an Ansible collection also imports + any variables associated with the collection - :expectedresults: - 1. Host should be assigned the proper role. - 2. Job report should be created. + :id: 7ff88022-fe9b-482f-a6bb-3922036a1e1c - :CaseImportance: Critical - """ - SELECTED_ROLE = 'RedHatInsights.insights-client' - if rhel_contenthost.os_version.major <= 7: - rhel_contenthost.create_custom_repos(rhel7=settings.repos.rhel7_os) - assert rhel_contenthost.execute('yum install -y insights-client').status == 0 - rhel_contenthost.install_katello_ca(target_sat) - rhel_contenthost.register_contenthost(module_org.label, force=True) - assert rhel_contenthost.subscribed - rhel_contenthost.add_rex_key(satellite=target_sat) - id = target_sat.nailgun_smart_proxy.id - target_host = rhel_contenthost.nailgun_host - target_sat.api.AnsibleRoles().sync(data={'proxy_id': id, 'role_names': [SELECTED_ROLE]}) - target_sat.cli.Host.ansible_roles_assign({'id': target_host.id, 'ansible-roles': SELECTED_ROLE}) - host_roles = target_host.list_ansible_roles() - assert host_roles[0]['name'] == SELECTED_ROLE - template_id = ( - target_sat.api.JobTemplate() - .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] - .id - ) - job = target_sat.api.JobInvocation().run( - synchronous=False, - data={ - 'job_template_id': template_id, - 'targeting_type': 'static_query', - 'search_query': f'name = {rhel_contenthost.hostname}', - }, - ) - target_sat.wait_for_tasks( - f'resource_type = JobInvocation and resource_id = {job["id"]}', poll_timeout=1000 - ) - result = target_sat.api.JobInvocation(id=job['id']).read() - assert result.succeeded == 1 - with session: - session.location.select(constants.DEFAULT_LOC) - assert session.host.search(target_host.name)[0]['Name'] == rhel_contenthost.hostname - session.configreport.search(rhel_contenthost.hostname) - session.configreport.delete(rhel_contenthost.hostname) - assert len(session.configreport.read()['table']) == 0 - - -@pytest.mark.no_containers -@pytest.mark.rhel_ver_match('9') -def test_positive_ansible_custom_role(target_sat, session, module_org, rhel_contenthost, request): - """ - Test Config report generation with Custom Ansible Role + :steps: + 1. Install an Ansible collection + 2. Navigate to Configure > Variables - :id: 3551068a-ccfc-481c-b7ec-8fe2b8a802bf + :expectedresults: Verify that any variables associated with the collection + are present on Configure > Variables - :customerscenario: true + :CaseAutomation: NotAutomated + """ - :steps: - 1. Register a content host with satellite - 2. Create a custom role and import into satellite - 3. Assign that role to a host - 4. Assert that the role was assigned to the host successfully - 5. Run the Ansible playbook associated with that role - 6. Check if the report is created successfully + @pytest.mark.stubbed + @pytest.mark.tier3 + def test_positive_set_ansible_role_order_per_host(self): + """Verify that role run order can be set and this order is respected when roles are run - :expectedresults: - 1. Config report should be generated for a custom role run. + :id: 24fbcd60-7cd1-46ff-86ac-16d6b436202c - :BZ: 2155392 + :steps: + 1. Enable a host for remote execution + 2. Navigate to Hosts > All Hosts > $hostname > Edit > Ansible Roles + 3. Assign more than one role to the host + 4. Use the drag-and-drop mechanism to change the order of the roles + 5. Run Ansible roles on the host - :CaseAutomation: Automated - """ - SELECTED_ROLE = 'custom_role' - playbook = f'{robottelo_tmp_dir}/playbook.yml' - data = { - 'name': 'Copy ssh keys', - 'copy': { - 'src': '/var/lib/foreman-proxy/ssh/{{ item }}', - 'dest': '/root/.ssh', - 'owner': 'root', - "group": 'root', - 'mode': '0400', - }, - 'with_items': ['id_rsa_foreman_proxy.pub', 'id_rsa_foreman_proxy'], - } - with open(playbook, 'w') as f: - yaml.dump(data, f, sort_keys=False, default_flow_style=False) - target_sat.execute('mkdir /etc/ansible/roles/custom_role') - target_sat.put(playbook, '/etc/ansible/roles/custom_role/playbook.yaml') - rhel_contenthost.install_katello_ca(target_sat) - rhel_contenthost.register_contenthost(module_org.label, force=True) - assert rhel_contenthost.subscribed - rhel_contenthost.add_rex_key(satellite=target_sat) - proxy_id = target_sat.nailgun_smart_proxy.id - target_host = rhel_contenthost.nailgun_host - target_sat.api.AnsibleRoles().sync(data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]}) - target_sat.cli.Host.ansible_roles_assign({'id': target_host.id, 'ansible-roles': SELECTED_ROLE}) - host_roles = target_host.list_ansible_roles() - assert host_roles[0]['name'] == SELECTED_ROLE - - template_id = ( - target_sat.api.JobTemplate() - .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] - .id - ) - job = target_sat.api.JobInvocation().run( - synchronous=False, - data={ - 'job_template_id': template_id, - 'targeting_type': 'static_query', - 'search_query': f'name = {rhel_contenthost.hostname}', - }, - ) - target_sat.wait_for_tasks( - f'resource_type = JobInvocation and resource_id = {job["id"]}', poll_timeout=1000 - ) - result = target_sat.api.JobInvocation(id=job['id']).read() - assert result.succeeded == 1 - with session: - session.location.select(constants.DEFAULT_LOC) - assert session.host.search(target_host.name)[0]['Name'] == rhel_contenthost.hostname - session.configreport.search(rhel_contenthost.hostname) - session.configreport.delete(rhel_contenthost.hostname) - assert len(session.configreport.read()['table']) == 0 - - @request.addfinalizer - def _finalize(): - result = target_sat.cli.Ansible.roles_delete({'name': SELECTED_ROLE}) - assert f'Ansible role [{SELECTED_ROLE}] was deleted.' in result[0]['message'] - target_sat.execute('rm -rvf /etc/ansible/roles/custom_role') - - -@pytest.mark.tier2 -def test_positive_host_role_information(target_sat, function_host): - """Assign Ansible Role to a Host and verify that the information - in the new UI is displayed correctly - - :id: 7da913ef-3b43-4bfa-9a45-d895431c8b56 - - :steps: - 1. Register a RHEL host to Satellite. - 2. Import all roles available by default. - 3. Assign one role to the RHEL host. - 4. Navigate to the new UI for the given Host. - 5. Select the 'Ansible' tab, then the 'Inventory' sub-tab. - - :expectedresults: Roles assigned directly to the Host are visible on the subtab. - """ - SELECTED_ROLE = 'RedHatInsights.insights-client' - - location = function_host.location.read() - organization = function_host.organization.read() - proxy_id = target_sat.nailgun_smart_proxy.id - target_sat.api.AnsibleRoles().sync(data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]}) - target_sat.cli.Host.ansible_roles_assign( - {'id': function_host.id, 'ansible-roles': SELECTED_ROLE} - ) - host_roles = function_host.list_ansible_roles() - assert host_roles[0]['name'] == SELECTED_ROLE - with target_sat.ui_session() as session: - session.location.select(location.name) - session.organization.select(organization.name) - ansible_roles_table = session.host_new.get_ansible_roles(function_host.name) - assert ansible_roles_table[0]["Name"] == SELECTED_ROLE - all_assigned_roles_table = session.host_new.get_ansible_roles_modal(function_host.name) - assert all_assigned_roles_table[0]["Name"] == SELECTED_ROLE - - -@pytest.mark.stubbed -@pytest.mark.tier2 -def test_positive_role_variable_information(self): - """Create and assign variables to an Ansible Role and verify that the information in - the new UI is displayed correctly - - :id: 4ab2813a-6b83-4907-b104-0473465814f5 - - :steps: - 1. Register a RHEL host to Satellite. - 2. Import all roles available by default. - 3. Create a host group and assign one of the Ansible roles to the host group. - 4. Assign the host to the host group. - 5. Assign one roles to the RHEL host. - 6. Create a variable and associate it with the role assigned to the Host. - 7. Create a variable and associate it with the role assigned to the Hostgroup. - 8. Navigate to the new UI for the given Host. - 9. Select the 'Ansible' tab, then the 'Variables' sub-tab. - - :expectedresults: The variables information for the given Host is visible. - """ + :expectedresults: The roles are run in the specified order + :CaseAutomation: NotAutomated + """ -@pytest.mark.stubbed -@pytest.mark.tier2 -def test_positive_assign_role_in_new_ui(self): - """Using the new Host UI, assign a role to a Host + @pytest.mark.stubbed + @pytest.mark.tier3 + def test_positive_set_ansible_role_order_per_hostgroup(self): + """Verify that role run order can be set and that this order is respected when roles are run - :id: 044f38b4-cff2-4ddc-b93c-7e9f2826d00d + :id: 9eb5bc8e-081a-45b9-8751-f4220c944da6 - :steps: - 1. Register a RHEL host to Satellite. - 2. Import all roles available by default. - 3. Navigate to the new UI for the given Host. - 4. Select the 'Ansible' tab - 5. Click the 'Assign Ansible Roles' button. - 6. Using the popup, assign a role to the Host. + :steps: + 1. Enable a host for remote execution + 2. Create a host group + 3. Navigate to Configure > Host Groups > $hostgroup > Ansible Roles + 4. Assign more than one role to the host group + 5. Use the drag-and-drop mechanism to change the order of the roles + 6. Add the host to the host group + 7. Run Ansible roles on the host group - :expectedresults: The Role is successfully assigned to the Host, and shows up on the UI - """ + :expectedresults: The roles are run in the specified order + + :CaseAutomation: NotAutomated + """ + + @pytest.mark.tier2 + def test_positive_assign_and_remove_ansible_role_to_host(self, target_sat, function_host): + """Add and remove the role(s) of a Host + :id: a61b4c05-1395-47c2-b6d9-fcff8b094e0e -@pytest.mark.stubbed -@pytest.mark.tier2 -def test_positive_remove_role_in_new_ui(self): - """Using the new Host UI, remove the role(s) of a Host + :setup: Used pre-defined function_host (component/host) registerd with satellite. - :id: d6de5130-45f6-4349-b490-fbde2aed082c + :steps: + 1. Import all roles available by default. + 2. Assign a role to the host. + 3. Navigate to the new UI for the given Host. + 4. Select the 'Ansible' tab + 5. Click the 'Edit Ansible roles' button. + 6. Using the popup, remove the assigned role from the Host. - :steps: - 1. Register a RHEL host to Satellite. - 2. Import all roles available by default. - 3. Assign a role to the host. - 4. Navigate to the new UI for the given Host. - 5. Select the 'Ansible' tab - 6. Click the 'Edit Ansible roles' button. - 7. Using the popup, remove the assigned role from the Host. + :expectedresults: The Role is successfully aaded and removed from the Host, and no longer shows + up on the UI + """ + SELECTED_ROLE = 'RedHatInsights.insights-client' - :expectedresults: The Role is successfully removed from the Host, and no longer shows - up on the UI + location = function_host.location.read() + organization = function_host.organization.read() + proxy_id = target_sat.nailgun_smart_proxy.id + target_sat.api.AnsibleRoles().sync( + data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]} + ) + with target_sat.ui_session() as session: + session.location.select(location.name) + session.organization.select(organization.name) + # add ansible role + session.host_new.add_single_ansible_role(function_host.name) + wait_for(lambda: session.browser.refresh(), timeout=5) + # verify ansible role assigned to new UI for the given Host + ansible_roles_table = session.host_new.get_ansible_roles(function_host.name) + assert ansible_roles_table[0]['Name'] == SELECTED_ROLE + # remove ansible role + session.host_new.remove_single_ansible_role(function_host.name) + # verify ansible role removed + result = session.host_new.get_details( + function_host.name, widget_names='ansible.roles.noRoleAssign' + ) + assert ( + result['ansible']['roles']['noRoleAssign'] + == 'No roles assigned directly to the host' + ) + + +class TestAnsibleREX: + """Test class for remote execution via Ansible + + :CaseComponent: Ansible-RemoteExecution """ + + @pytest.mark.tier2 + @pytest.mark.pit_server + @pytest.mark.no_containers + @pytest.mark.rhel_ver_match('[^6]') + def test_positive_config_report_ansible( + self, target_sat, module_org, module_ak_with_cv, rhel_contenthost + ): + """Test Config Report generation with Ansible Jobs. + + :id: 118e25e5-409e-44ba-b908-217da9722576 + + :steps: + 1. Register a content host with satellite + 2. Import a role into satellite + 3. Assign that role to a host + 4. Assert that the role was assigned to the host successfully + 5. Run the Ansible playbook associated with that role + 6. Check if the report is created successfully + + :expectedresults: + 1. Host should be assigned the proper role. + 2. Job report should be created. + """ + SELECTED_ROLE = 'RedHatInsights.insights-client' + if rhel_contenthost.os_version.major <= 7: + rhel_contenthost.create_custom_repos(rhel7=settings.repos.rhel7_os) + assert rhel_contenthost.execute('yum install -y insights-client').status == 0 + result = rhel_contenthost.register(module_org, None, module_ak_with_cv.name, target_sat) + assert result.status == 0, f'Failed to register host: {result.stderr}' + id = target_sat.nailgun_smart_proxy.id + target_host = rhel_contenthost.nailgun_host + target_sat.api.AnsibleRoles().sync(data={'proxy_id': id, 'role_names': [SELECTED_ROLE]}) + target_sat.cli.Host.ansible_roles_assign( + {'id': target_host.id, 'ansible-roles': SELECTED_ROLE} + ) + host_roles = target_host.list_ansible_roles() + assert host_roles[0]['name'] == SELECTED_ROLE + template_id = ( + target_sat.api.JobTemplate() + .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] + .id + ) + job = target_sat.api.JobInvocation().run( + synchronous=False, + data={ + 'job_template_id': template_id, + 'targeting_type': 'static_query', + 'search_query': f'name = {rhel_contenthost.hostname}', + }, + ) + target_sat.wait_for_tasks( + f'resource_type = JobInvocation and resource_id = {job["id"]}', poll_timeout=1000 + ) + result = target_sat.api.JobInvocation(id=job['id']).read() + assert result.succeeded == 1 + with target_sat.ui_session() as session: + session.organization.select(module_org.name) + session.location.select(constants.DEFAULT_LOC) + assert session.host.search(target_host.name)[0]['Name'] == rhel_contenthost.hostname + session.configreport.search(rhel_contenthost.hostname) + session.configreport.delete(rhel_contenthost.hostname) + assert len(session.configreport.read()['table']) == 0 + + @pytest.mark.no_containers + @pytest.mark.rhel_ver_match('9') + def test_positive_ansible_custom_role( + self, target_sat, module_org, module_ak_with_cv, rhel_contenthost, request + ): + """ + Test Config report generation with Custom Ansible Role + + :id: 3551068a-ccfc-481c-b7ec-8fe2b8a802bf + + :steps: + 1. Register a content host with satellite + 2. Create a custom role and import into satellite + 3. Assign that role to a host + 4. Assert that the role was assigned to the host successfully + 5. Run the Ansible playbook associated with that role + 6. Check if the report is created successfully + + :expectedresults: + 1. Config report should be generated for a custom role run. + + :BZ: 2155392 + + :customerscenario: true + """ + + @request.addfinalizer + def _finalize(): + result = target_sat.cli.Ansible.roles_delete({'name': SELECTED_ROLE}) + assert f'Ansible role [{SELECTED_ROLE}] was deleted.' in result[0]['message'] + target_sat.execute('rm -rvf /etc/ansible/roles/custom_role') + + SELECTED_ROLE = 'custom_role' + playbook = f'{robottelo_tmp_dir}/playbook.yml' + data = { + 'name': 'Copy ssh keys', + 'copy': { + 'src': '/var/lib/foreman-proxy/ssh/{{ item }}', + 'dest': '/root/.ssh', + 'owner': 'root', + "group": 'root', + 'mode': '0400', + }, + 'with_items': ['id_rsa_foreman_proxy.pub', 'id_rsa_foreman_proxy'], + } + with open(playbook, 'w') as f: + yaml.dump(data, f, sort_keys=False, default_flow_style=False) + target_sat.execute('mkdir /etc/ansible/roles/custom_role') + target_sat.put(playbook, '/etc/ansible/roles/custom_role/playbook.yaml') + + result = rhel_contenthost.register(module_org, None, module_ak_with_cv.name, target_sat) + assert result.status == 0, f'Failed to register host: {result.stderr}' + proxy_id = target_sat.nailgun_smart_proxy.id + target_host = rhel_contenthost.nailgun_host + target_sat.api.AnsibleRoles().sync( + data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]} + ) + target_sat.cli.Host.ansible_roles_assign( + {'id': target_host.id, 'ansible-roles': SELECTED_ROLE} + ) + host_roles = target_host.list_ansible_roles() + assert host_roles[0]['name'] == SELECTED_ROLE + + template_id = ( + target_sat.api.JobTemplate() + .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] + .id + ) + job = target_sat.api.JobInvocation().run( + synchronous=False, + data={ + 'job_template_id': template_id, + 'targeting_type': 'static_query', + 'search_query': f'name = {rhel_contenthost.hostname}', + }, + ) + target_sat.wait_for_tasks( + f'resource_type = JobInvocation and resource_id = {job["id"]}', poll_timeout=1000 + ) + result = target_sat.api.JobInvocation(id=job['id']).read() + assert result.succeeded == 1 + with target_sat.ui_session() as session: + session.organization.select(module_org.name) + session.location.select(constants.DEFAULT_LOC) + assert session.host.search(target_host.name)[0]['Name'] == rhel_contenthost.hostname + session.configreport.search(rhel_contenthost.hostname) + session.configreport.delete(rhel_contenthost.hostname) + assert len(session.configreport.read()['table']) == 0 + + @pytest.mark.stubbed + @pytest.mark.tier3 + def test_positive_ansible_job_check_mode(self): + """Run a job on a host with enable_roles_check_mode parameter enabled + + :id: 7aeb7253-e555-4e28-977f-71f16d3c32e2 + + :steps: + 1. Set the value of the ansible_roles_check_mode parameter to true on a host + 2. Associate one or more Ansible roles with the host + 3. Run Ansible roles against the host + + :expectedresults: Verify that the roles were run in check mode + (i.e. no changes were made on the host) + + :CaseAutomation: NotAutomated + """ + + @pytest.mark.stubbed + @pytest.mark.tier3 + def test_positive_install_ansible_collection_via_job_invocation(self): + """Verify that Ansible collections can be installed on hosts via job invocations + + :id: d4096aef-f6fc-41b6-ae56-d19b1f49cd42 + + :steps: + 1. Enable a host for remote execution + 2. Navigate to Hosts > Schedule Remote Job + 3. Select "Ansible Galaxy" as the job category + 4. Select "Ansible Collection - Install from Galaxy" as the job template + 5. Enter a collection in the ansible_collections_list field + 6. Click "Submit" + + :expectedresults: The Ansible collection is successfully installed on the host + + :CaseAutomation: NotAutomated + """ + + @pytest.mark.stubbed + @pytest.mark.tier2 + def test_positive_schedule_recurring_host_job(self): + """Using the new Host UI, schedule a recurring job on a Host + + :id: 5052be04-28ab-4349-8bee-851ef76e4ffa + + :steps: + 1. Register a RHEL host to Satellite. + 2. Import all roles available by default. + 3. Assign a role to host. + 4. Navigate to the new UI for the given Host. + 5. Select the Jobs subtab. + 6. Click the Schedule Recurring Job button, and using the popup, schedule a + recurring Job. + 7. Navigate to Job Invocations. + + :expectedresults: Scheduled Job appears in the Job Invocation list at the appointed time + """ + + @pytest.mark.stubbed + @pytest.mark.tier2 + def test_positive_schedule_recurring_hostgroup_job(self): + """Using the new recurring job scheduler, schedule a recurring job on a Hostgroup + + :id: c65db99b-11fe-4a32-89d0-0a4692b07efe + + :steps: + 1. Register a RHEL host to Satellite. + 2. Import all roles available by default. + 3. Assign a role to host. + 4. Navigate to the Host Group page. + 5. Select the "Configure Ansible Job" action. + 6. Click the Schedule Recurring Job button, and using the popup, schedule a + recurring Job. + 7. Navigate to Job Invocations. + + :expectedresults: Scheduled Job appears in the Job Invocation list at the appointed time + """ diff --git a/tests/foreman/ui/test_bookmarks.py b/tests/foreman/ui/test_bookmarks.py index f2622ae84a8..096f8fdf7cd 100644 --- a/tests/foreman/ui/test_bookmarks.py +++ b/tests/foreman/ui/test_bookmarks.py @@ -11,40 +11,39 @@ :CaseImportance: High """ -from airgun.exceptions import NoSuchElementException +from airgun.exceptions import DisabledWidgetError, NoSuchElementException from fauxfactory import gen_string import pytest from robottelo.config import user_nailgun_config -from robottelo.constants import BOOKMARK_ENTITIES -from robottelo.utils.issue_handlers import is_open +from robottelo.constants import BOOKMARK_ENTITIES_SELECTION @pytest.fixture( - scope='module', params=BOOKMARK_ENTITIES, ids=(i['name'] for i in BOOKMARK_ENTITIES) + scope='module', + params=BOOKMARK_ENTITIES_SELECTION, + ids=(i['name'] for i in BOOKMARK_ENTITIES_SELECTION), ) def ui_entity(module_org, module_location, request): """Collects the list of all applicable UI entities for testing and does all required preconditions. """ entity = request.param + entity_name, entity_setup = entity['name'], entity.get('setup') + # Skip the entities, which can't be tested ATM (not implemented in + # airgun) + skip = entity.get('skip_for_ui') + if skip: + pytest.skip(f'{entity_name} not implemented in airgun') # Some pages require at least 1 existing entity for search bar to # appear. Creating 1 entity for such pages - entity_name, entity_setup = entity['name'], entity.get('setup') if entity_setup: - # Skip the entities, which can't be tested ATM (not implemented in - # airgun or have open BZs) - skip = entity.get('skip_for_ui') - if isinstance(skip, tuple | list): - open_issues = {issue for issue in skip if is_open(issue)} - pytest.skip(f'There is/are an open issue(s) {open_issues} with entity {entity_name}') # entities with 1 organization and location if entity_name in ('Host',): entity_setup(organization=module_org, location=module_location).create() # entities with no organizations and locations elif entity_name in ( 'ComputeProfile', - 'GlobalParameter', 'HardwareModel', 'UserGroup', ): @@ -117,7 +116,7 @@ def test_positive_create_bookmark_public( public_name = gen_string('alphanumeric') nonpublic_name = gen_string('alphanumeric') with session: - ui_lib = getattr(session, ui_entity['name'].lower()) + ui_lib = getattr(session, ui_entity['session_name']) for name in (public_name, nonpublic_name): ui_lib.create_bookmark( {'name': name, 'query': gen_string('alphanumeric'), 'public': name == public_name} @@ -259,8 +258,14 @@ def test_negative_create_with_duplicate_name(session, ui_entity, module_target_s existing_bookmark = session.bookmark.search(bookmark.name)[0] assert existing_bookmark['Name'] == bookmark.name ui_lib = getattr(session, ui_entity['name'].lower()) - # this fails but does not raise UI error, BZ#1992652 closed wontfix - ui_lib.create_bookmark({'name': bookmark.name, 'query': query, 'public': True}) + # this fails but does not raise UI error in old style dialog, BZ#1992652 closed + # wontfix, but new style dialog raises error, both situations occur + old_ui = ui_entity.get('old_ui') + if old_ui: + ui_lib.create_bookmark({'name': bookmark.name, 'query': query, 'public': True}) + else: + with pytest.raises((DisabledWidgetError, NoSuchElementException)): + ui_lib.create_bookmark({'name': bookmark.name, 'query': query, 'public': True}) # assert there are no duplicate bookmarks new_search = session.bookmark.search(bookmark.name) assert len(new_search) == 1 diff --git a/tests/foreman/ui/test_capsulecontent.py b/tests/foreman/ui/test_capsulecontent.py index f2e8db514e3..43fc34b430f 100644 --- a/tests/foreman/ui/test_capsulecontent.py +++ b/tests/foreman/ui/test_capsulecontent.py @@ -229,4 +229,4 @@ def test_positive_content_counts_for_mixed_cv( # Remove the LCEs from Capsule and ensure they are not listed anymore. session.capsule.edit(module_capsule_configured.hostname, remove_all_lces=True) details = session.capsule.read_details(module_capsule_configured.hostname) - assert 'content' not in details.keys(), 'Content still listed for removed LCEs' + assert 'content' not in details, 'Content still listed for removed LCEs' diff --git a/tests/foreman/ui/test_computeresource_gce.py b/tests/foreman/ui/test_computeresource_gce.py index 73f4fb11daf..a25edb1da44 100644 --- a/tests/foreman/ui/test_computeresource_gce.py +++ b/tests/foreman/ui/test_computeresource_gce.py @@ -161,8 +161,17 @@ def test_positive_gce_provision_end_to_end( :expectedresults: Host is provisioned successfully """ + name = f'test{gen_string("alpha", 4).lower()}' hostname = f'{name}.{gce_domain.name}' + + @request.addfinalizer + def _finalize(): + gcehost = sat_gce.api.Host().search(query={'search': f'name={hostname}'}) + if gcehost: + gcehost[0].delete() + googleclient.disconnect() + gceapi_vmname = hostname.replace('.', '-') root_pwd = gen_string('alpha', 15) storage = [{'size': 20}] @@ -214,13 +223,6 @@ def test_positive_gce_provision_end_to_end( # 2.2 GCE Backend Assertions assert gceapi_vm.is_stopping or gceapi_vm.is_stopped - @request.addfinalizer - def _finalize(): - gcehost = sat_gce.api.Host().search(query={'search': f'name={hostname}'}) - if gcehost: - gcehost[0].delete() - googleclient.disconnect() - @pytest.mark.tier4 @pytest.mark.upgrade @@ -247,6 +249,14 @@ def test_positive_gce_cloudinit_provision_end_to_end( """ name = f'test{gen_string("alpha", 4).lower()}' hostname = f'{name}.{gce_domain.name}' + + @request.addfinalizer + def _finalize(): + gcehost = sat_gce.api.Host().search(query={'search': f'name={hostname}'}) + if gcehost: + gcehost[0].delete() + googleclient.disconnect() + gceapi_vmname = hostname.replace('.', '-') storage = [{'size': 20}] root_pwd = gen_string('alpha', random.choice([8, 15])) @@ -290,10 +300,3 @@ def test_positive_gce_cloudinit_provision_end_to_end( assert not sat_gce.api.Host().search(query={'search': f'name="{hostname}"'}) # 2.2 GCE Backend Assertions assert gceapi_vm.is_stopping or gceapi_vm.is_stopped - - @request.addfinalizer - def _finalize(): - gcehost = sat_gce.api.Host().search(query={'search': f'name={hostname}'}) - if gcehost: - gcehost[0].delete() - googleclient.disconnect() diff --git a/tests/foreman/ui/test_computeresource_libvirt.py b/tests/foreman/ui/test_computeresource_libvirt.py index 518e26817b8..42477b8d46a 100644 --- a/tests/foreman/ui/test_computeresource_libvirt.py +++ b/tests/foreman/ui/test_computeresource_libvirt.py @@ -171,10 +171,8 @@ def test_positive_provision_end_to_end( } ) name = f'{hostname}.{module_libvirt_provisioning_sat.domain.name}' - assert session.host.search(name)[0]['Name'] == name - - # teardown request.addfinalizer(lambda: sat.provisioning_cleanup(name)) + assert session.host.search(name)[0]['Name'] == name # Check on Libvirt, if VM exists result = sat.execute( diff --git a/tests/foreman/ui/test_computeresource_vmware.py b/tests/foreman/ui/test_computeresource_vmware.py index ddf7ca5f33e..4c9516e9cd6 100644 --- a/tests/foreman/ui/test_computeresource_vmware.py +++ b/tests/foreman/ui/test_computeresource_vmware.py @@ -16,7 +16,7 @@ import pytest from wait_for import TimedOutError, wait_for -from wrapanapi.systems.virtualcenter import VMWareSystem, vim +from wrapanapi.systems.virtualcenter import vim from robottelo.config import settings from robottelo.constants import ( @@ -26,6 +26,7 @@ VMWARE_CONSTANTS, ) from robottelo.utils.datafactory import gen_string +from robottelo.utils.issue_handlers import is_open pytestmark = [pytest.mark.skip_if_not_set('vmware')] @@ -55,20 +56,18 @@ def _get_normalized_size(size): return f'{size} {suffixes[suffix_index]}' -def _get_vmware_datastore_summary_string(data_store_name=settings.vmware.datastore, vmware=None): +@pytest.fixture +def get_vmware_datastore_summary_string(vmware, vmwareclient): """Return the datastore string summary for data_store_name For "Local-Ironforge" datastore the string looks Like: "Local-Ironforge (free: 1.66 TB, prov: 2.29 TB, total: 2.72 TB)" """ - system = VMWareSystem( - hostname=vmware.hostname, - username=settings.vmware.username, - password=settings.vmware.password, - ) data_store_summary = [ - h for h in system.get_obj_list(vim.Datastore) if h.host and h.name == data_store_name + h + for h in vmwareclient.get_obj_list(vim.Datastore) + if h.host and h.name == settings.vmware.datastore ][0].summary uncommitted = data_store_summary.uncommitted or 0 capacity = _get_normalized_size(data_store_summary.capacity) @@ -76,12 +75,13 @@ def _get_vmware_datastore_summary_string(data_store_name=settings.vmware.datasto prov = _get_normalized_size( data_store_summary.capacity + uncommitted - data_store_summary.freeSpace ) - return f'{data_store_name} (free: {free_space}, prov: {prov}, total: {capacity})' + return f'{settings.vmware.datastore} (free: {free_space}, prov: {prov}, total: {capacity})' +@pytest.mark.e2e @pytest.mark.tier1 @pytest.mark.parametrize('vmware', ['vmware7', 'vmware8'], indirect=True) -def test_positive_end_to_end(session, module_org, module_location, vmware, module_target_sat): +def test_positive_cr_end_to_end(session, module_org, module_location, vmware, module_target_sat): """Perform end-to-end testing for compute resource VMware component. :id: 47fc9e77-5b22-46b4-a76c-3217434fde2f @@ -289,32 +289,65 @@ def test_positive_resource_vm_power_management(session, vmware): raise AssertionError('Timed out waiting for VM to toggle power state') from err +@pytest.mark.e2e +@pytest.mark.upgrade @pytest.mark.tier2 @pytest.mark.parametrize('vmware', ['vmware7', 'vmware8'], indirect=True) -def test_positive_select_vmware_custom_profile_guest_os_rhel7(session, vmware): - """Select custom default (3-Large) compute profile guest OS RHEL7. +def test_positive_vmware_custom_profile_end_to_end( + session, vmware, request, target_sat, get_vmware_datastore_summary_string +): + """Perform end to end testing for VMware compute profile. :id: 24f7bb5f-2aaf-48cb-9a56-d2d0713dfe3d :customerscenario: true - :setup: vmware hostname and credentials. - :steps: 1. Create a compute resource of type vmware. - 2. Provide valid hostname, username and password. - 3. Select the created vmware CR. - 4. Click Compute Profile tab. - 5. Select 3-Large profile - 6. Set Guest OS field to RHEL7 OS. + 2. Update a compute profile with all values - :expectedresults: Guest OS RHEL7 is selected successfully. + :expectedresults: Compute profiles are updated successfully with all the values. - :BZ: 1315277 + :BZ: 1315277, 2266672 """ cr_name = gen_string('alpha') - guest_os_name = 'Red Hat Enterprise Linux 7 (64-bit)' + guest_os_names = [ + 'Red Hat Enterprise Linux 7 (64-bit)', + 'Red Hat Enterprise Linux 8 (64 bit)', + 'Red Hat Enterprise Linux 9 (64 bit)', + ] + compute_profile = ['1-Small', '2-Medium', '3-Large'] + cpus = ['2', '4', '6'] + vm_memory = ['4000', '6000', '8000'] + annotation_notes = gen_string('alpha') + firmware_type = ['Automatic', 'BIOS', 'EFI'] + resource_pool = VMWARE_CONSTANTS['pool'] + folder = VMWARE_CONSTANTS['folder'] + virtual_hw_version = VMWARE_CONSTANTS['virtualhw_version'] + memory_hot_add = True + cpu_hot_add = True + cdrom_drive = True + disk_size = '10 GB' + network = 'VLAN 1001' # hardcoding network here as this test won't be doing actual provisioning + storage_data = { + 'storage': { + 'controller': VMWARE_CONSTANTS['scsicontroller'], + 'disks': [ + { + 'data_store': get_vmware_datastore_summary_string, + 'size': disk_size, + 'thin_provision': True, + } + ], + } + } + network_data = { + 'network_interfaces': { + 'nic_type': VMWARE_CONSTANTS['network_interface_name'], + 'network': network, + } + } with session: session.computeresource.create( { @@ -326,143 +359,65 @@ def test_positive_select_vmware_custom_profile_guest_os_rhel7(session, vmware): 'provider_content.datacenter.value': settings.vmware.datacenter, } ) - assert session.computeresource.search(cr_name)[0]['Name'] == cr_name - session.computeresource.update_computeprofile( - cr_name, COMPUTE_PROFILE_LARGE, {'provider_content.guest_os': guest_os_name} - ) - values = session.computeresource.read_computeprofile(cr_name, COMPUTE_PROFILE_LARGE) - assert values['provider_content']['guest_os'] == guest_os_name - - -@pytest.mark.tier2 -@pytest.mark.parametrize('vmware', ['vmware7', 'vmware8'], indirect=True) -def test_positive_access_vmware_with_custom_profile(session, vmware): - """Associate custom default (3-Large) compute profile - - :id: 751ef765-5091-4322-a0d9-0c9c73009cc4 - - :setup: vmware hostname and credentials. - :steps: - - 1. Create a compute resource of type vmware. - 2. Provide valid hostname, username and password. - 3. Select the created vmware CR. - 4. Click Compute Profile tab. - 5. Edit (3-Large) with valid configurations and submit. + @request.addfinalizer + def _finalize(): + cr = target_sat.api.VMWareComputeResource().search(query={'search': f'name={cr_name}'}) + if cr: + target_sat.api.VMWareComputeResource(id=cr[0].id).delete() - :expectedresults: The Compute Resource created and associated to compute profile (3-Large) - with provided values. - """ - cr_name = gen_string('alpha') - data_store_summary_string = _get_vmware_datastore_summary_string(vmware=vmware) - cr_profile_data = dict( - cpus='2', - cores_per_socket='2', - memory='1024', - firmware='EFI', - cluster=settings.vmware.cluster, - resource_pool=VMWARE_CONSTANTS.get('pool'), - folder=VMWARE_CONSTANTS.get('folder'), - guest_os=VMWARE_CONSTANTS.get('guest_os'), - virtual_hw_version=VMWARE_CONSTANTS.get('virtualhw_version'), - memory_hot_add=True, - cpu_hot_add=True, - cdrom_drive=True, - annotation_notes=gen_string('alpha'), - network_interfaces=[] - if not settings.provisioning.vlan_id - else [ - dict( - nic_type=VMWARE_CONSTANTS.get('network_interface_name'), - network='VLAN 1001', # hardcoding network here as these test won't be doing actual provisioning - ), - dict( - nic_type=VMWARE_CONSTANTS.get('network_interface_name'), - network='VLAN 1001', - ), - ], - storage=[ - dict( - controller=VMWARE_CONSTANTS.get('scsicontroller'), - disks=[ - dict( - data_store=data_store_summary_string, - size='10 GB', - thin_provision=True, - ), - dict( - data_store=data_store_summary_string, - size='20 GB', - thin_provision=False, - eager_zero=False, - ), - ], - ), - dict( - controller=VMWARE_CONSTANTS.get('scsicontroller'), - disks=[ - dict( - data_store=data_store_summary_string, - size='30 GB', - thin_provision=False, - eager_zero=True, - ) - ], - ), - ], - ) - with session: - session.computeresource.create( - { - 'name': cr_name, - 'provider': FOREMAN_PROVIDERS['vmware'], - 'provider_content.vcenter': vmware.hostname, - 'provider_content.user': settings.vmware.username, - 'provider_content.password': settings.vmware.password, - 'provider_content.datacenter.value': settings.vmware.datacenter, - } - ) assert session.computeresource.search(cr_name)[0]['Name'] == cr_name - session.computeresource.update_computeprofile( - cr_name, - COMPUTE_PROFILE_LARGE, - {f'provider_content.{key}': value for key, value in cr_profile_data.items()}, - ) - values = session.computeresource.read_computeprofile(cr_name, COMPUTE_PROFILE_LARGE) - provider_content = values['provider_content'] - # assert main compute resource profile data updated successfully. - excluded_keys = ['network_interfaces', 'storage'] - expected_value = { - key: value for key, value in cr_profile_data.items() if key not in excluded_keys - } - provided_value = { - key: value for key, value in provider_content.items() if key in expected_value - } - assert provided_value == expected_value - # assert compute resource profile network data updated successfully. - for network_index, expected_network_value in enumerate( - cr_profile_data['network_interfaces'] + for guest_os_name, cprofile, cpu, memory, firmware in zip( + guest_os_names, compute_profile, cpus, vm_memory, firmware_type, strict=True ): - provided_network_value = { - key: value - for key, value in provider_content['network_interfaces'][network_index].items() - if key in expected_network_value - } - assert provided_network_value == expected_network_value - # assert compute resource profile storage data updated successfully. - for controller_index, expected_controller_value in enumerate(cr_profile_data['storage']): - provided_controller_value = provider_content['storage'][controller_index] + session.computeresource.update_computeprofile( + cr_name, + cprofile, + { + 'provider_content.guest_os': guest_os_name, + 'provider_content.cpus': cpu, + 'provider_content.memory': memory, + 'provider_content.cluster': settings.vmware.cluster, + 'provider_content.annotation_notes': annotation_notes, + 'provider_content.virtual_hw_version': virtual_hw_version, + 'provider_content.firmware': firmware, + 'provider_content.resource_pool': resource_pool, + 'provider_content.folder': folder, + 'provider_content.memory_hot_add': memory_hot_add, + 'provider_content.cpu_hot_add': cpu_hot_add, + 'provider_content.cdrom_drive': cdrom_drive, + 'provider_content.storage': [value for value in storage_data.values()], + 'provider_content.network_interfaces': [ + value for value in network_data.values() + ], + }, + ) + values = session.computeresource.read_computeprofile(cr_name, cprofile) + provider_content = values['provider_content'] + assert provider_content['guest_os'] == guest_os_name + assert provider_content['cpus'] == cpu + assert provider_content['memory'] == memory + assert provider_content['cluster'] == settings.vmware.cluster + assert provider_content['annotation_notes'] == annotation_notes + assert provider_content['virtual_hw_version'] == virtual_hw_version + if not is_open('BZ:2266672'): + assert values['provider_content']['firmware'] == firmware + assert provider_content['resource_pool'] == resource_pool + assert provider_content['folder'] == folder + assert provider_content['memory_hot_add'] == memory_hot_add + assert provider_content['cpu_hot_add'] == cpu_hot_add + assert provider_content['cdrom_drive'] == cdrom_drive assert ( - provided_controller_value['controller'] == expected_controller_value['controller'] + provider_content['storage'][0]['controller'] == VMWARE_CONSTANTS['scsicontroller'] ) - for disk_index, expected_disk_value in enumerate(expected_controller_value['disks']): - provided_disk_value = { - key: value - for key, value in provided_controller_value['disks'][disk_index].items() - if key in expected_disk_value - } - assert provided_disk_value == expected_disk_value + assert provider_content['storage'][0]['disks'][0]['size'] == disk_size + assert ( + provider_content['network_interfaces'][0]['nic_type'] + == VMWARE_CONSTANTS['network_interface_name'] + ) + assert provider_content['network_interfaces'][0]['network'] == network + session.computeresource.delete(cr_name) + assert not session.computeresource.search(cr_name) @pytest.mark.tier2 diff --git a/tests/foreman/ui/test_containerimagetag.py b/tests/foreman/ui/test_containerimagetag.py index 579ce53f3e9..41f8960d96d 100644 --- a/tests/foreman/ui/test_containerimagetag.py +++ b/tests/foreman/ui/test_containerimagetag.py @@ -70,5 +70,5 @@ def test_positive_search(session, module_org, module_product, module_repository) None, ) assert module_product.name == repo_line['Product'] - assert DEFAULT_CV == repo_line['Content View'] + assert repo_line['Content View'] == DEFAULT_CV assert 'Success' in repo_line['Last Sync'] diff --git a/tests/foreman/ui/test_contenthost.py b/tests/foreman/ui/test_contenthost.py index ffbe571c5b2..5f351076e5b 100644 --- a/tests/foreman/ui/test_contenthost.py +++ b/tests/foreman/ui/test_contenthost.py @@ -116,7 +116,7 @@ def get_rhel_lifecycle_support(rhel_version): rhel_lifecycle_status = 'Unknown' if rhel_version not in rhels: return rhel_lifecycle_status - elif rhels.index(rhel_version) <= 1: + if rhels.index(rhel_version) <= 1: rhel_lifecycle_status = 'Full support' elif rhels.index(rhel_version) == 2: rhel_lifecycle_status = 'Approaching end of maintenance support' diff --git a/tests/foreman/ui/test_dashboard.py b/tests/foreman/ui/test_dashboard.py index b55891475ef..5cc044e1e5c 100644 --- a/tests/foreman/ui/test_dashboard.py +++ b/tests/foreman/ui/test_dashboard.py @@ -193,7 +193,7 @@ def test_positive_task_status(session, target_sat): ) def test_positive_user_access_with_host_filter( test_name, - function_entitlement_manifest_org, + function_sca_manifest_org, module_location, rhel_contenthost, target_sat, @@ -221,7 +221,7 @@ def test_positive_user_access_with_host_filter( """ user_login = gen_string('alpha') user_password = gen_string('alphanumeric') - org = function_entitlement_manifest_org + org = function_sca_manifest_org lce = target_sat.api.LifecycleEnvironment(organization=org).create() # create a role with necessary permissions role = target_sat.api.Role().create() diff --git a/tests/foreman/ui/test_discoveredhost.py b/tests/foreman/ui/test_discoveredhost.py index 92ea28d185a..64476188f89 100644 --- a/tests/foreman/ui/test_discoveredhost.py +++ b/tests/foreman/ui/test_discoveredhost.py @@ -40,8 +40,7 @@ def _is_host_reachable(host, retries=12, iteration_sleep=5, expect_reachable=Tru result = ssh.command(cmd.format(retries, host, operator, iteration_sleep)) if expect_reachable: return not result.status - else: - return bool(result.status) + return bool(result.status) @pytest.mark.tier3 diff --git a/tests/foreman/ui/test_errata.py b/tests/foreman/ui/test_errata.py index 2495399be13..dadcaf6bef9 100644 --- a/tests/foreman/ui/test_errata.py +++ b/tests/foreman/ui/test_errata.py @@ -233,8 +233,8 @@ def registered_contenthost( Using SCA and global registration. :note: rhel_contenthost will be parameterized by rhel6 to 9, also -fips for all distros. - to use specific rhel version parameterized contenthost, use pytest.mark.rhel_ver_match() - for marking contenthost version(s) for tests using this fixture. + to use specific rhel version parameterized contenthost; + use `pytest.mark.rhel_ver_match()` to mark contenthost version(s) for tests using this fixture. :repos: pass as a parameterized request list of upstream URLs for custom repositories. @@ -245,7 +245,6 @@ def registered_contenthost( indirect=True, ) """ - # read indirect parameterization request, could be None try: repos = getattr(request, 'param', repos).copy() except AttributeError: @@ -655,10 +654,7 @@ def test_host_content_errata_tab_pagination( pf4_pagination = session.host_new.get_errata_pagination(_chost_name) assert (_read_page := pf4_pagination.read()) assert _read_page != _prior_pagination - # assert per_page and total_pages remained the same - assert pf4_pagination.current_per_page == 5 assert pf4_pagination.current_page == 1 - assert pf4_pagination.total_pages > 1 # total_items decreased by one item_count = pf4_pagination.total_items assert item_count == _prior_app_count - 1 @@ -726,14 +722,24 @@ def test_host_content_errata_tab_pagination( @pytest.mark.tier2 @pytest.mark.skipif((not settings.robottelo.REPOS_HOSTING_URL), reason='Missing repos_hosting_url') -def test_positive_list(session, function_org_with_parameter, lce, target_sat): +def test_positive_list( + module_sca_manifest_org, + function_org, + function_lce, + target_sat, + module_lce, + module_cv, + module_ak, + session, +): """View all errata in an Org :id: 71c7a054-a644-4c1e-b304-6bc34ea143f4 - :Setup: Errata synced on satellite server. - - :steps: Create two Orgs each having a product synced which contains errata. + :steps: + 1. Setup two separate organization fixtures, function and module scope. + 2. Create and sync separate repositories for each org. + 3. Go to UI > Content Types > Errata page. :expectedresults: Check that the errata belonging to one Org is not showing in the other. @@ -741,23 +747,51 @@ def test_positive_list(session, function_org_with_parameter, lce, target_sat): :customerscenario: true """ - org = function_org_with_parameter - rc = target_sat.cli_factory.RepositoryCollection( - repositories=[target_sat.cli_factory.YumRepository(settings.repos.yum_3.url)] + _org_module = module_sca_manifest_org + _org_function = function_org + module_cv = module_cv.read() # for module sca org + # create and sync repository, for module org's errata + target_sat.cli_factory.setup_org_for_a_custom_repo( + { + 'url': CUSTOM_REPO_URL, + 'organization-id': _org_module.id, + 'lifecycle-environment-id': module_lce.id, + 'activationkey-id': module_ak.id, + 'content-view-id': module_cv.id, + }, ) - rc.setup_content(org.id, lce.id) + # create and sync repository, for function org's errata + target_sat.cli_factory.setup_org_for_a_custom_repo( + { + 'url': CUSTOM_REPO_3_URL, + 'organization-id': _org_function.id, + 'lifecycle-environment-id': function_lce.id, + }, + ) + with session: + # View in module org + session.organization.select(org_name=_org_module.name) assert ( session.errata.search(CUSTOM_REPO_ERRATA_ID, applicable=False)[0]['Errata ID'] == CUSTOM_REPO_ERRATA_ID + ), f'Could not find expected errata: {CUSTOM_REPO_ERRATA_ID}, in module org: {_org_module.name}.' + + assert not session.errata.search(CUSTOM_REPO_3_ERRATA_ID, applicable=False), ( + f'Found function org ({_org_function.name}) errata: {CUSTOM_REPO_3_ERRATA_ID},' + f' in module org ({_org_module.name}) as well.' ) - assert not session.errata.search(settings.repos.yum_3.errata[5], applicable=False) - session.organization.select(org_name=org.name) + # View in function org + session.organization.select(org_name=_org_function.name) assert ( - session.errata.search(settings.repos.yum_3.errata[5], applicable=False)[0]['Errata ID'] - == settings.repos.yum_3.errata[5] + session.errata.search(CUSTOM_REPO_3_ERRATA_ID, applicable=False)[0]['Errata ID'] + == CUSTOM_REPO_3_ERRATA_ID + ), f'Could not find expected errata: {CUSTOM_REPO_3_ERRATA_ID}, in function org: {_org_function.name}.' + + assert not session.errata.search(CUSTOM_REPO_ERRATA_ID, applicable=False), ( + f'Found module org ({_org_module.name}) errata: {CUSTOM_REPO_ERRATA_ID},' + f' in function org ({_org_function.name}) as well.' ) - assert not session.errata.search(CUSTOM_REPO_ERRATA_ID, applicable=False) @pytest.mark.tier2 @@ -1157,59 +1191,109 @@ def test_positive_content_host_search_type(session, erratatype_vm): @pytest.mark.tier3 +@pytest.mark.rhel_ver_match('8') @pytest.mark.parametrize( - 'module_repos_collection_with_setup', - [ - { - 'distro': 'rhel7', - 'SatelliteToolsRepository': {}, - 'RHELAnsibleEngineRepository': {'cdn': True}, - 'YumRepository': {'url': settings.repos.yum_9.url}, - } - ], + 'registered_contenthost', + [[CUSTOM_REPO_URL]], indirect=True, ) -def test_positive_show_count_on_content_host_page( - session, module_org_with_parameter, erratatype_vm -): - """Available errata count displayed in Content hosts page +def test_positive_show_count_on_host_pages(session, module_org, registered_contenthost): + """Available errata by type displayed in New Host>Errata page, + and expected count by type in Legacy>Content hosts page. :id: 8575e282-d56e-41dc-80dd-f5f6224417cb :Setup: - 1. Errata synced on satellite server. - 2. Some content hosts are present. + 1. Errata synced on satellite server from custom repository. + 2. Registered host, subscribed to promoted CVV, with repo synced to appliable custom packages and erratum. + + :steps: - :steps: Go to Hosts -> Content Hosts. + 1. Go to Hosts -> All Hosts, and Legacy ContentHost -> Hosts. + 2. None of the erratum are installable. + 3. Install all outdated applicable packages via yum. + 4. Recalculate errata applicablity for host. + 5. All of the erratum are now installable, on both pages from step 1. - :expectedresults: The available errata count is displayed. + :expectedresults: + The available errata count is displayed and updates. + Displayed erratum match between the two content host pages. :BZ: 1484044, 1775427 :customerscenario: true """ - vm = erratatype_vm + vm = registered_contenthost hostname = vm.hostname + assert vm.subscribed + assert vm.execute('subscription-manager repos').status == 0 + assert vm.applicable_errata_count == 0 + with session: session.location.select(loc_name=DEFAULT_LOC) + # new host UI + new_host_values = session.host_new.search(hostname) + assert new_host_values[0]['Name'] == hostname + # None of the erratum are installable + for errata_type in ('Security', 'Bugfix', 'Enhancement'): + installable_errata = None + empty_table = False + try: + # exception will be raised in case of unfound element (dropdown or dropdown entry) + # if an exception was raised, the table is missing/empty (no errata). + installable_errata = session.host_new.get_errata_by_type( + entity_name=hostname, + type=errata_type, + )['content']['errata']['table'] + except Exception: + empty_table = True + assert ( + not installable_errata + ), f'Found some installable {errata_type} errata, when none were expected.' + assert empty_table + # legacy contenthost UI content_host_values = session.contenthost.search(hostname) assert content_host_values[0]['Name'] == hostname installable_errata = content_host_values[0]['Installable Updates']['errata'] - for errata_type in ('security', 'bug_fix', 'enhancement'): - assert int(installable_errata[errata_type]) == 0 + assert ( + int(installable_errata[errata_type]) == 0 + ), f'Found some installable {errata_type} errata, when none were expected.' + # install outdated packages, recalculate errata applicability pkgs = ' '.join(FAKE_9_YUM_OUTDATED_PACKAGES) assert vm.execute(f'yum install -y {pkgs}').status == 0 - + assert vm.execute('subscription-manager repos').status == 0 + assert vm.applicable_errata_count == 5 + + # new host UI (errata tab) + new_host_values = session.host_new.search(hostname) + assert new_host_values[0]['Name'] == hostname + # erratum are installable + security_errata = session.host_new.get_errata_by_type( + entity_name=hostname, + type='Security', + )['content']['errata']['table'] + assert len(security_errata) == FAKE_9_YUM_SECURITY_ERRATUM_COUNT + for errata_type in ('Bugfix', 'Enhancement'): + installable_errata = session.host_new.get_errata_by_type( + entity_name=hostname, + type=errata_type, + )['content']['errata']['table'] + assert ( + len(installable_errata) == 1 + ), f'Expected only one {errata_type} errata to be installable.' + # legacy contenthost UI content_host_values = session.contenthost.search(hostname) assert content_host_values[0]['Name'] == hostname installable_errata = content_host_values[0]['Installable Updates']['errata'] - + # erratum are installable assert int(installable_errata['security']) == FAKE_9_YUM_SECURITY_ERRATUM_COUNT for errata_type in ('bug_fix', 'enhancement'): - assert int(installable_errata[errata_type]) == 1 + assert ( + int(installable_errata[errata_type]) == 1 + ), f'Expected only one {errata_type} errata to be installable.' @pytest.mark.tier3 diff --git a/tests/foreman/ui/test_host.py b/tests/foreman/ui/test_host.py index b3564fb04b1..7fec6db1f7d 100644 --- a/tests/foreman/ui/test_host.py +++ b/tests/foreman/ui/test_host.py @@ -58,9 +58,45 @@ def ui_user(ui_user, smart_proxy_location, module_target_sat): return ui_user +@pytest.fixture +def ui_admin_user(target_sat): + """Admin user.""" + admin_user = target_sat.api.User().search( + query={'search': f'login={settings.server.admin_username}'} + )[0] + admin_user.password = settings.server.admin_password + + return admin_user + + +@pytest.fixture +def ui_view_hosts_user(target_sat, current_sat_org, current_sat_location): + """User with View hosts role.""" + role = target_sat.api.Role().search(query={'search': 'name="View hosts"'})[0] + password = gen_string('alphanumeric') + user = target_sat.api.User( + admin=False, + location=[current_sat_location], + organization=[current_sat_org], + role=[role], + password=password, + ).create() + user.password = password + + yield user + + user.delete() + + +@pytest.fixture(params=['ui_admin_user', 'ui_view_hosts_user']) +def ui_hosts_columns_user(request): + """Parametrized fixture returning defined users for the UI session.""" + return request.getfixturevalue(request.param) + + @pytest.fixture def scap_policy(scap_content, target_sat): - scap_policy = target_sat.cli_factory.make_scap_policy( + return target_sat.cli_factory.make_scap_policy( { 'name': gen_string('alpha'), 'deploy-by': 'ansible', @@ -70,7 +106,6 @@ def scap_policy(scap_content, target_sat): 'weekday': OSCAP_WEEKDAY['friday'].lower(), } ) - return scap_policy second_scap_policy = scap_policy @@ -1058,44 +1093,9 @@ def test_positive_read_details_page_from_new_ui(session, host_ui_options): @pytest.mark.tier4 -@pytest.mark.rhel_ver_match('8') -def test_rex_new_ui(session, target_sat, rex_contenthost): - """Run remote execution using the new host details page - - :id: ee625595-4995-43b2-9e6d-633c9b33ff93 - - :steps: - 1. Navigate to Overview tab - 2. Schedule a job - 3. Wait for the job to finish - 4. Job is visible in Recent jobs card - - :expectedresults: Remote execution succeeded and the job is visible on Recent jobs card on - Overview tab - """ - hostname = rex_contenthost.hostname - job_args = { - 'job_category': 'Commands', - 'job_template': 'Run Command - Script Default', - 'template_content.command': 'ls', - } - with session: - session.location.select(loc_name=DEFAULT_LOC) - session.host_new.schedule_job(hostname, job_args) - task_result = target_sat.wait_for_tasks( - search_query=(f'Remote action: Run ls on {hostname}'), - search_rate=2, - max_tries=30, - ) - task_status = target_sat.api.ForemanTask(id=task_result[0].id).poll() - assert task_status['result'] == 'success' - recent_jobs = session.host_new.get_details(hostname, "overview.recent_jobs")['overview'] - assert "Run ls" == recent_jobs['recent_jobs']['finished']['table'][0]['column0'] - assert "succeeded" == recent_jobs['recent_jobs']['finished']['table'][0]['column2'] - - -@pytest.mark.tier4 -def test_positive_manage_table_columns(session, current_sat_org, current_sat_location): +def test_positive_manage_table_columns( + target_sat, test_name, ui_hosts_columns_user, current_sat_org, current_sat_location +): """Set custom columns of the hosts table. :id: e5e18982-cc43-11ed-8562-000c2989e153 @@ -1108,7 +1108,7 @@ def test_positive_manage_table_columns(session, current_sat_org, current_sat_loc :expectedresults: Check if the custom columns were set properly, i.e., are displayed or not displayed in the table. - :BZ: 1813274 + :BZ: 1813274, 2212499 :customerscenario: true """ @@ -1128,9 +1128,11 @@ def test_positive_manage_table_columns(session, current_sat_org, current_sat_loc 'Boot time': True, 'Recommendations': False, } - with session: - session.organization.select(org_name=current_sat_org) - session.location.select(loc_name=current_sat_location) + with target_sat.ui_session( + test_name, ui_hosts_columns_user.login, ui_hosts_columns_user.password + ) as session: + session.organization.select(org_name=current_sat_org.name) + session.location.select(loc_name=current_sat_location.name) session.host.manage_table_columns(columns) displayed_columns = session.host.get_displayed_table_headers() for column, is_displayed in columns.items(): @@ -1161,8 +1163,8 @@ def test_positive_host_details_read_templates( host = target_sat.api.Host().search(query={'search': f'name={target_sat.hostname}'})[0] api_templates = [template['name'] for template in host.list_provisioning_templates()] with session: - session.organization.select(org_name=current_sat_org) - session.location.select(loc_name=current_sat_location) + session.organization.select(org_name=current_sat_org.name) + session.location.select(loc_name=current_sat_location.name) host_detail = session.host_new.get_details(target_sat.hostname, widget_names='details') ui_templates = [ row['column1'].strip() @@ -1216,18 +1218,18 @@ def test_positive_update_delete_package( if not is_open('BZ:2132680'): product_name = module_repos_collection_with_setup.custom_product.name repos = session.host_new.get_repo_sets(client.hostname, product_name) - assert 'Enabled' == repos[0].status + assert repos[0].status == 'Enabled' session.host_new.override_repo_sets( client.hostname, product_name, "Override to disabled" ) - assert 'Disabled' == repos[0].status + assert repos[0].status == 'Disabled' session.host_new.install_package(client.hostname, FAKE_8_CUSTOM_PACKAGE_NAME) result = client.run(f'yum install -y {FAKE_7_CUSTOM_PACKAGE}') assert result.status != 0 session.host_new.override_repo_sets( client.hostname, product_name, "Override to enabled" ) - assert 'Enabled' == repos[0].status + assert repos[0].status == 'Enabled' # refresh repos on system client.run('subscription-manager repos') # install package @@ -1284,7 +1286,7 @@ def test_positive_update_delete_package( task_status = target_sat.api.ForemanTask(id=task_result[0].id).poll() assert task_status['result'] == 'success' packages = session.host_new.get_packages(client.hostname, FAKE_8_CUSTOM_PACKAGE_NAME) - assert 'table' not in packages.keys() + assert 'table' not in packages result = client.run(f'rpm -q {FAKE_8_CUSTOM_PACKAGE}') assert result.status != 0 @@ -1361,7 +1363,7 @@ def test_positive_apply_erratum( assert task_status['result'] == 'success' # verify values = session.host_new.get_details(client.hostname, widget_names='content.errata') - assert 'table' not in values['content']['errata'].keys() + assert 'table' not in values['content']['errata'] result = client.run( 'yum update --assumeno --security | grep "No packages needed for security"' ) diff --git a/tests/foreman/ui/test_hostcollection.py b/tests/foreman/ui/test_hostcollection.py index 546e70faae0..b6c90d685ec 100644 --- a/tests/foreman/ui/test_hostcollection.py +++ b/tests/foreman/ui/test_hostcollection.py @@ -93,10 +93,9 @@ def vm_host_collection(module_target_sat, module_org_with_parameter, vm_content_ module_target_sat.api.Host().search(query={'search': f'name={host.hostname}'})[0].id for host in vm_content_hosts ] - host_collection = module_target_sat.api.HostCollection( + return module_target_sat.api.HostCollection( host=host_ids, organization=module_org_with_parameter ).create() - return host_collection @pytest.fixture @@ -107,10 +106,9 @@ def vm_host_collection_module_stream( module_target_sat.api.Host().search(query={'search': f'name={host.hostname}'})[0].id for host in vm_content_hosts_module_stream ] - host_collection = module_target_sat.api.HostCollection( + return module_target_sat.api.HostCollection( host=host_ids, organization=module_org_with_parameter ).create() - return host_collection def _run_remote_command_on_content_hosts(command, vm_clients): @@ -136,7 +134,7 @@ def _is_package_installed( if result.status == 0 and expect_installed: installed += 1 break - elif result.status != 0 and not expect_installed: + if result.status != 0 and not expect_installed: installed -= 1 break if ind < retries - 1: @@ -146,8 +144,7 @@ def _is_package_installed( if expect_installed: return installed == len(vm_clients) - else: - return bool(installed) + return bool(installed) def _install_package_with_assertion(vm_clients, package_name): diff --git a/tests/foreman/ui/test_http_proxy.py b/tests/foreman/ui/test_http_proxy.py index 9e3f1e5b10b..10d2249be30 100644 --- a/tests/foreman/ui/test_http_proxy.py +++ b/tests/foreman/ui/test_http_proxy.py @@ -11,11 +11,23 @@ :CaseAutomation: Automated """ +from box import Box from fauxfactory import gen_integer, gen_string, gen_url import pytest from robottelo.config import settings -from robottelo.constants import DOCKER_REPO_UPSTREAM_NAME, REPO_TYPE +from robottelo.constants import DOCKER_REPO_UPSTREAM_NAME, REPO_TYPE, REPOS +from robottelo.hosts import ProxyHostError + + +@pytest.fixture +def function_spec_char_user(target_sat, session_auth_proxy): + """Creates a user with special character password on the auth HTTP proxy""" + name = gen_string('alpha').lower() # lower! + passwd = gen_string('punctuation').replace("'", '') + session_auth_proxy.add_user(name, passwd) + yield Box(name=name, passwd=passwd) + session_auth_proxy.remove_user(name) @pytest.mark.tier2 @@ -26,8 +38,6 @@ def test_positive_create_update_delete(module_org, module_location, target_sat): :id: 0c7cdf3d-778f-427a-9a2f-42ad7c23aa15 :expectedresults: All expected CRUD actions finished successfully - - :CaseImportance: High """ http_proxy_name = gen_string('alpha', 15) updated_proxy_name = gen_string('alpha', 15) @@ -53,8 +63,8 @@ def test_positive_create_update_delete(module_org, module_location, target_sat): assert http_proxy_values['http_proxy']['name'] == http_proxy_name assert http_proxy_values['http_proxy']['url'] == http_proxy_url assert http_proxy_values['http_proxy']['username'] == username - assert http_proxy_values['locations']['resources']['assigned'][0] == module_location.name - assert http_proxy_values['organizations']['resources']['assigned'][0] == module_org.name + assert module_location.name in http_proxy_values['locations']['resources']['assigned'] + assert module_org.name in http_proxy_values['organizations']['resources']['assigned'] # Update http_proxy with new name session.http_proxy.update(http_proxy_name, {'http_proxy.name': updated_proxy_name}) assert session.http_proxy.search(updated_proxy_name)[0]['Name'] == updated_proxy_name @@ -198,7 +208,7 @@ def test_set_default_http_proxy(module_org, module_location, setting_update, tar :steps: 1. Navigate to Infrastructure > Http Proxies 2. Create a Http Proxy - 3. GoTo to Administer > Settings > content tab + 3. Go to Administer > Settings > Content tab 4. Update the "Default HTTP Proxy" with created above. 5. Update "Default HTTP Proxy" to "no global default". @@ -239,29 +249,30 @@ def test_set_default_http_proxy(module_org, module_location, setting_update, tar def test_check_http_proxy_value_repository_details( function_org, function_location, function_product, setting_update, target_sat ): - """Deleted Global Http Proxy is reflected in repository details page". + """Global Http Proxy is reflected in repository details page". :id: 3f64255a-ef6c-4acb-b99b-e5579133b564 :steps: 1. Create Http Proxy (Go to Infrastructure > Http Proxies > New Http Proxy) - 2. GoTo to Administer > Settings > content tab + 2. Go to Administer > Settings > Content tab 3. Update the "Default HTTP Proxy" with created above. - 4. Create repository with Global Default Http Proxy. - 5. Delete the Http Proxy + 4. Create repository, check the Global Default Http Proxy is used. + 5. Delete the Http Proxy. + 6. Check it no longer appears on the Settings and repository page. :BZ: 1820193 :parametrized: yes :expectedresults: - 1. After deletion of "Default Http Proxy" its field on settings page should be - set to no global defult - 2. "HTTP Proxy" field in repository details page should be set to Global Default (None). + 1. Repository is automatically created with relevant Global Default Http Proxy. + 2. After Http Proxy deletion + - its field on Settings page should be set to Empty. + - "HTTP Proxy" field in repository details page should be set to Global Default (None). :CaseImportance: Medium """ - property_name = setting_update.name repo_name = gen_string('alpha') http_proxy_a = target_sat.api.HTTPProxy( @@ -285,45 +296,102 @@ def test_check_http_proxy_value_repository_details( 'repo_content.upstream_url': settings.repos.yum_0.url, }, ) + repo_values = session.repository.read(function_product.name, repo_name) + assert ( + repo_values['repo_content']['http_proxy_policy'] + == f'Global Default ({http_proxy_a.name})' + ) + session.http_proxy.delete(http_proxy_a.name) result = session.settings.read(f'name = {property_name}') assert result['table'][0]['Value'] == "Empty" - session.repository.search(function_product.name, repo_name)[0]['Name'] repo_values = session.repository.read(function_product.name, repo_name) assert repo_values['repo_content']['http_proxy_policy'] == 'Global Default (None)' @pytest.mark.tier3 @pytest.mark.run_in_one_thread -@pytest.mark.stubbed -def test_http_proxy_containing_special_characters(): +def test_http_proxy_containing_special_characters( + request, + target_sat, + session_auth_proxy, + function_spec_char_user, + module_sca_manifest_org, + default_location, +): """Test Manifest refresh and redhat repository sync with http proxy special characters in password. :id: 16082c6a-9320-4a9a-bd6c-5687b099c940 - :customerscenario: true + :setup: + 1. Have an authenticated HTTP proxy. + 2. At the Proxy side create a user with special characters in password + (via function_spec_user fixture), let's call him the spec-char user. :steps: - 1. Navigate to Infrastructure > Http Proxies - 2. Create HTTP Proxy with special characters in password. - 3. Go To to Administer > Settings > content tab - 4. Fill the details related to HTTP Proxy and click on "Test connection" button. - 5. Update the "Default HTTP Proxy" with created above. - 6. Refresh manifest. - 7. Enable and sync any redhat repositories. - - :BZ: 1844840 + 1. Check that no logs exist for the spec-char user at the proxy side yet. + 2. Create a proxy via UI using the spec-char user. + 3. Update settings to use the proxy for the content ops. + 4. Refresh the manifest, check it went through the proxy. + 5. Enable and sync some RH repository, check it went through the proxy. :expectedresults: - 1. "Test connection" button workes as expected. - 2. Manifest refresh, repository enable/disable and repository sync operation - finished successfully. + 1. HTTP proxy can be created via UI using the spec-char user. + 2. Manifest refresh, repository enable and sync succeed and are performed + through the HTTP proxy. - :CaseAutomation: NotAutomated + :BZ: 1844840 - :CaseImportance: High + :customerscenario: true """ + # Check that no logs exist for the spec-char user at the proxy side yet. + with pytest.raises(ProxyHostError): + session_auth_proxy.get_log(tail=100, grep=function_spec_char_user.name) + + # Create a proxy via UI using the spec-char user. + proxy_name = gen_string('alpha') + with target_sat.ui_session() as session: + session.organization.select(org_name=module_sca_manifest_org.name) + session.http_proxy.create( + { + 'http_proxy.name': proxy_name, + 'http_proxy.url': settings.http_proxy.auth_proxy_url, + 'http_proxy.username': function_spec_char_user.name, + 'http_proxy.password': function_spec_char_user.passwd, + 'locations.resources.assigned': [default_location.name], + 'organizations.resources.assigned': [module_sca_manifest_org.name], + } + ) + request.addfinalizer( + lambda: target_sat.api.HTTPProxy() + .search(query={'search': f'name={proxy_name}'})[0] + .delete() + ) + + # Update settings to use the proxy for the content ops. + session.settings.update( + 'name = content_default_http_proxy', + f'{proxy_name} ({settings.http_proxy.auth_proxy_url})', + ) + + # Refresh the manifest, check it went through the proxy. + target_sat.cli.Subscription.refresh_manifest( + {'organization-id': module_sca_manifest_org.id} + ) + assert session_auth_proxy.get_log( + tail=100, grep=f'CONNECT subscription.rhsm.redhat.com.*{function_spec_char_user.name}' + ), 'RHSM connection not found in proxy log' + + # Enable and sync some RH repository, check it went through the proxy. + repo_id = target_sat.api_factory.enable_sync_redhat_repo( + REPOS['rhae2'], module_sca_manifest_org.id + ) + repo = target_sat.api.Repository(id=repo_id).read() + assert session_auth_proxy.get_log( + tail=100, grep=f'CONNECT cdn.redhat.com.*{function_spec_char_user.name}' + ), 'CDN connection not found in proxy log' + assert repo.content_counts['rpm'] > 0, 'Where is my content?!' @pytest.mark.tier2 diff --git a/tests/foreman/ui/test_jobinvocation.py b/tests/foreman/ui/test_jobinvocation.py deleted file mode 100644 index c98e512f527..00000000000 --- a/tests/foreman/ui/test_jobinvocation.py +++ /dev/null @@ -1,171 +0,0 @@ -"""Test class for Job Invocation procedure - -:Requirement: JobInvocation - -:CaseAutomation: Automated - -:CaseComponent: RemoteExecution - -:Team: Endeavour - -:CaseImportance: High - -""" -from inflection import camelize -import pytest - -from robottelo.utils.datafactory import gen_string - - -@pytest.fixture -def module_rhel_client_by_ip(module_org, smart_proxy_location, rhel7_contenthost, target_sat): - """Setup a broker rhel client to be used in remote execution by ip""" - rhel7_contenthost.configure_rex(satellite=target_sat, org=module_org) - target_sat.api_factory.update_vm_host_location( - rhel7_contenthost, location_id=smart_proxy_location.id - ) - return rhel7_contenthost - - -@pytest.mark.tier4 -def test_positive_run_default_job_template_by_ip( - session, module_org, smart_proxy_location, module_rhel_client_by_ip -): - """Run a job template on a host connected by ip - - :id: 9a90aa9a-00b4-460e-b7e6-250360ee8e4d - - :Setup: Use pre-defined job template. - - :steps: - - 1. Set remote_execution_connect_by_ip on host to true - 2. Navigate to an individual host and click Run Job - 3. Select the job and appropriate template - 4. Run the job - - :expectedresults: Verify the job was successfully ran against the host - - :parametrized: yes - """ - hostname = module_rhel_client_by_ip.hostname - with session: - session.organization.select(module_org.name) - session.location.select(smart_proxy_location.name) - assert session.host.search(hostname)[0]['Name'] == hostname - session.jobinvocation.run( - { - 'job_category': 'Commands', - 'job_template': 'Run Command - Script Default', - 'search_query': f'name ^ {hostname}', - 'template_content.command': 'ls', - } - ) - session.jobinvocation.wait_job_invocation_state(entity_name='Run ls', host_name=hostname) - status = session.jobinvocation.read(entity_name='Run ls', host_name=hostname) - assert status['overview']['hosts_table'][0]['Status'] == 'success' - - -@pytest.mark.tier4 -def test_positive_run_custom_job_template_by_ip( - session, module_org, smart_proxy_location, module_rhel_client_by_ip -): - """Run a job template on a host connected by ip - - :id: e283ae09-8b14-4ce1-9a76-c1bbd511d58c - - :Setup: Create a working job template. - - :steps: - - 1. Set remote_execution_connect_by_ip on host to true - 2. Navigate to an individual host and click Run Job - 3. Select the job and appropriate template - 4. Run the job - - :expectedresults: Verify the job was successfully ran against the host - - :parametrized: yes - """ - hostname = module_rhel_client_by_ip.hostname - job_template_name = gen_string('alpha') - with session: - session.organization.select(module_org.name) - session.location.select(smart_proxy_location.name) - assert session.host.search(hostname)[0]['Name'] == hostname - session.jobtemplate.create( - { - 'template.name': job_template_name, - 'template.template_editor.rendering_options': 'Editor', - 'template.template_editor.editor': '<%= input("command") %>', - 'job.provider_type': 'Script', - 'inputs': [{'name': 'command', 'required': True, 'input_type': 'User input'}], - } - ) - assert session.jobtemplate.search(job_template_name)[0]['Name'] == job_template_name - session.jobinvocation.run( - { - 'job_category': 'Miscellaneous', - 'job_template': job_template_name, - 'search_query': f'name ^ {hostname}', - 'template_content.command': 'ls', - } - ) - job_description = f'{camelize(job_template_name.lower())} with inputs command="ls"' - session.jobinvocation.wait_job_invocation_state( - entity_name=job_description, host_name=hostname - ) - status = session.jobinvocation.read(entity_name=job_description, host_name=hostname) - assert status['overview']['hosts_table'][0]['Status'] == 'success' - - -@pytest.mark.stubbed -@pytest.mark.tier2 -def test_positive_schedule_recurring_host_job(self): - """Using the new Host UI, schedule a recurring job on a Host - - :id: 5052be04-28ab-4349-8bee-851ef76e4ffa - - :caseComponent: Ansible - - :Team: Rocket - - :steps: - 1. Register a RHEL host to Satellite. - 2. Import all roles available by default. - 3. Assign a role to host. - 4. Navigate to the new UI for the given Host. - 5. Select the Jobs subtab. - 6. Click the Schedule Recurring Job button, and using the popup, schedule a - recurring Job. - 7. Navigate to Job Invocations. - - :expectedresults: The scheduled Job appears in the Job Invocation list at the appointed - time - """ - - -@pytest.mark.stubbed -@pytest.mark.tier2 -def test_positive_schedule_recurring_hostgroup_job(self): - """Using the new recurring job scheduler, schedule a recurring job on a Hostgroup - - :id: c65db99b-11fe-4a32-89d0-0a4692b07efe - - :caseComponent: Ansible - - :Team: Rocket - - :steps: - 1. Register a RHEL host to Satellite. - 2. Import all roles available by default. - 3. Assign a role to host. - 4. Navigate to the Host Group page. - 5. Select the "Configure Ansible Job" action. - 6. Click the Schedule Recurring Job button, and using the popup, schedule a - recurring Job. - 7. Navigate to Job Invocations. - - :expectedresults: The scheduled Job appears in the Job Invocation list at the appointed - time - """ diff --git a/tests/foreman/ui/test_ldap_authentication.py b/tests/foreman/ui/test_ldap_authentication.py index b75d3206e90..554244957fe 100644 --- a/tests/foreman/ui/test_ldap_authentication.py +++ b/tests/foreman/ui/test_ldap_authentication.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: LDAP +:CaseComponent: Authentication :Team: Endeavour @@ -406,9 +406,8 @@ def test_positive_delete_external_roles( ) with target_sat.ui_session( test_name, ldap_data['ldap_user_name'], ldap_data['ldap_user_passwd'] - ) as ldapsession: - with pytest.raises(NavigationTriesExceeded): - ldapsession.location.create({'name': gen_string('alpha')}) + ) as ldapsession, pytest.raises(NavigationTriesExceeded): + ldapsession.location.create({'name': gen_string('alpha')}) @pytest.mark.parametrize('ldap_auth_source', ['AD', 'IPA'], indirect=True) @@ -677,7 +676,7 @@ def test_positive_add_katello_role_with_org( results = session.activationkey.search(ak_name) assert results[0]['Name'] == ak_name session.organization.select(different_org.name) - assert not session.activationkey.search(ak_name)[0]['Name'] == ak_name + assert session.activationkey.search(ak_name)[0]['Name'] != ak_name ak = ( target_sat.api.ActivationKey(organization=module_org) .search(query={'search': f'name={ak_name}'})[0] @@ -761,9 +760,8 @@ def test_positive_login_user_basic_roles( target_sat.api_factory.create_role_permissions(role, permissions) with target_sat.ui_session( test_name, ldap_data['ldap_user_name'], ldap_data['ldap_user_passwd'] - ) as ldapsession: - with pytest.raises(NavigationTriesExceeded): - ldapsession.usergroup.search('') + ) as ldapsession, pytest.raises(NavigationTriesExceeded): + ldapsession.usergroup.search('') with session: session.user.update(ldap_data['ldap_user_name'], {'roles.resources.assigned': [role.name]}) with target_sat.ui_session( @@ -796,9 +794,8 @@ def test_positive_login_user_password_otp( ) with target_sat.ui_session( test_name, default_ipa_host.ipa_otp_username, otp_pass - ) as ldapsession: - with pytest.raises(NavigationTriesExceeded): - ldapsession.user.search('') + ) as ldapsession, pytest.raises(NavigationTriesExceeded): + ldapsession.user.search('') users = target_sat.api.User().search( query={'search': f'login="{default_ipa_host.ipa_otp_username}"'} ) @@ -1215,12 +1212,11 @@ def test_userlist_with_external_admin( # verify the users count with local admin and remote/external admin with target_sat.ui_session( user=idm_admin, password=settings.server.ssh_password - ) as remote_admin_session: - with target_sat.ui_session( - user=settings.server.admin_username, password=settings.server.admin_password - ) as local_admin_session: - assert local_admin_session.user.search(idm_user)[0]['Username'] == idm_user - assert remote_admin_session.user.search(idm_user)[0]['Username'] == idm_user + ) as remote_admin_session, target_sat.ui_session( + user=settings.server.admin_username, password=settings.server.admin_password + ) as local_admin_session: + assert local_admin_session.user.search(idm_user)[0]['Username'] == idm_user + assert remote_admin_session.user.search(idm_user)[0]['Username'] == idm_user @pytest.mark.skip_if_open('BZ:1883209') diff --git a/tests/foreman/ui/test_organization.py b/tests/foreman/ui/test_organization.py index cbca5ac3131..7b200391f77 100644 --- a/tests/foreman/ui/test_organization.py +++ b/tests/foreman/ui/test_organization.py @@ -22,7 +22,7 @@ @pytest.fixture(scope='module') -def module_repos_col(request, module_entitlement_manifest_org, module_lce, module_target_sat): +def module_repos_col(request, module_sca_manifest_org, module_lce, module_target_sat): repos_collection = module_target_sat.cli_factory.RepositoryCollection( repositories=[ # As Satellite Tools may be added as custom repo and to have a "Fully entitled" host, @@ -30,15 +30,15 @@ def module_repos_col(request, module_entitlement_manifest_org, module_lce, modul module_target_sat.cli_factory.YumRepository(url=settings.repos.yum_0.url), ], ) - repos_collection.setup_content(module_entitlement_manifest_org.id, module_lce.id) + repos_collection.setup_content(module_sca_manifest_org.id, module_lce.id) yield repos_collection @request.addfinalizer def _cleanup(): try: module_target_sat.api.Subscription( - organization=module_entitlement_manifest_org - ).delete_manifest(data={'organization_id': module_entitlement_manifest_org.id}) + organization=module_sca_manifest_org + ).delete_manifest(data={'organization_id': module_sca_manifest_org.id}) except Exception: logger.exception('Exception cleaning manifest:') @@ -245,7 +245,7 @@ def test_positive_update_compresource(session, module_target_sat): @pytest.mark.skip_if_not_set('fake_manifest') @pytest.mark.tier2 @pytest.mark.upgrade -def test_positive_delete_with_manifest_lces(session, target_sat, function_entitlement_manifest_org): +def test_positive_delete_with_manifest_lces(session, target_sat, function_sca_manifest_org): """Create Organization with valid values and upload manifest. Then try to delete that organization. @@ -255,7 +255,7 @@ def test_positive_delete_with_manifest_lces(session, target_sat, function_entitl :CaseImportance: Critical """ - org = function_entitlement_manifest_org + org = function_sca_manifest_org with session: session.organization.select(org.name) session.lifecycleenvironment.create({'name': 'DEV'}) @@ -269,9 +269,7 @@ def test_positive_delete_with_manifest_lces(session, target_sat, function_entitl @pytest.mark.tier2 @pytest.mark.upgrade -def test_positive_download_debug_cert_after_refresh( - session, target_sat, function_entitlement_manifest_org -): +def test_positive_download_debug_cert_after_refresh(session, target_sat, function_sca_manifest_org): """Create organization with valid manifest. Download debug certificate for that organization and refresh added manifest for few times in a row @@ -282,7 +280,7 @@ def test_positive_download_debug_cert_after_refresh( :CaseImportance: High """ - org = function_entitlement_manifest_org + org = function_sca_manifest_org try: with session: session.organization.select(org.name) @@ -343,24 +341,4 @@ def test_positive_product_view_organization_switch(session, module_org, module_p with session: assert session.product.search(module_product.name) session.organization.select(org_name="Default Organization") - assert not session.product.search(module_product.name) == module_product.name - - -def test_positive_prepare_for_sca_only_organization(target_sat, function_entitlement_manifest_org): - """Verify that the organization details page notifies users that Simple Content Access - will be required for all organizations in Satellite 6.16 - - :id: 3a6a848b-3c16-4dbb-8f52-5ea57a9a97ef - - :expectedresults: The Organization details page notifies users that Simple Content Access will - be required for all organizations in Satellite 6.16 - """ - with target_sat.ui_session() as session: - session.organization.select(function_entitlement_manifest_org.name) - sca_alert = session.organization.read( - function_entitlement_manifest_org.name, widget_names='primary' - ) - assert ( - 'Simple Content Access will be required for all organizations in Satellite 6.16.' - in sca_alert['primary']['sca_alert'] - ) + assert session.product.search(module_product.name) != module_product.name diff --git a/tests/foreman/ui/test_oscapcontent.py b/tests/foreman/ui/test_oscapcontent.py index 5669fb9e854..cb580c4c857 100644 --- a/tests/foreman/ui/test_oscapcontent.py +++ b/tests/foreman/ui/test_oscapcontent.py @@ -11,24 +11,13 @@ :CaseImportance: High """ -import os import pytest -from robottelo.config import robottelo_tmp_dir, settings from robottelo.constants import DataFile from robottelo.utils.datafactory import gen_string -@pytest.fixture(scope='module') -def oscap_content_path(module_target_sat): - _, file_name = os.path.split(settings.oscap.content_path) - - local_file = robottelo_tmp_dir.joinpath(file_name) - module_target_sat.get(remote_path=settings.oscap.content_path, local_path=str(local_file)) - return local_file - - @pytest.mark.skip_if_open("BZ:2167937") @pytest.mark.skip_if_open("BZ:2133151") @pytest.mark.tier1 diff --git a/tests/foreman/ui/test_provisioningtemplate.py b/tests/foreman/ui/test_provisioningtemplate.py index bc88d687f88..da2b8446dce 100644 --- a/tests/foreman/ui/test_provisioningtemplate.py +++ b/tests/foreman/ui/test_provisioningtemplate.py @@ -200,7 +200,7 @@ def test_positive_verify_supported_templates_rhlogo(target_sat, module_org, modu with target_sat.ui_session() as session: session.organization.select(org_name=module_org.name) session.location.select(loc_name=module_location.name) - for template in random_templates.keys(): + for template in random_templates: assert ( session.provisioningtemplate.is_locked(template) == random_templates[template]['locked'] diff --git a/tests/foreman/ui/test_registration.py b/tests/foreman/ui/test_registration.py index 22c34545ac7..9d223340a35 100644 --- a/tests/foreman/ui/test_registration.py +++ b/tests/foreman/ui/test_registration.py @@ -14,13 +14,16 @@ import re from airgun.exceptions import DisabledWidgetError -from airgun.session import Session from fauxfactory import gen_string import pytest from robottelo import constants from robottelo.config import settings -from robottelo.constants import FAKE_1_CUSTOM_PACKAGE, FAKE_7_CUSTOM_PACKAGE, REPO_TYPE +from robottelo.constants import ( + FAKE_1_CUSTOM_PACKAGE, + FAKE_7_CUSTOM_PACKAGE, + REPO_TYPE, +) pytestmark = pytest.mark.tier1 @@ -64,9 +67,9 @@ def test_positive_verify_default_values_for_global_registration( @pytest.mark.tier2 def test_positive_org_loc_change_for_registration( module_activation_key, - module_org, + module_sca_manifest_org, module_location, - target_sat, + module_target_sat, ): """Changing the organization and location to check if correct org and loc is updated on the global registration page as well as in the command @@ -76,15 +79,20 @@ def test_positive_org_loc_change_for_registration( :CaseImportance: Medium """ - new_org = target_sat.api.Organization().create() - new_loc = target_sat.api.Location().create() - target_sat.api.ActivationKey(organization=new_org).create() - with target_sat.ui_session() as session: - session.organization.select(org_name=module_org.name) + new_org = module_target_sat.api.Organization().create() + new_loc = module_target_sat.api.Location().create() + ak = module_target_sat.api.ActivationKey(organization=new_org).create() + with module_target_sat.ui_session() as session: + session.organization.select(org_name=module_sca_manifest_org.name) session.location.select(loc_name=module_location.name) - cmd = session.host.get_register_command() + cmd = session.host.get_register_command( + { + 'general.activation_keys': module_activation_key.name, + 'general.insecure': True, + } + ) expected_pairs = [ - f'organization_id={module_org.id}', + f'organization_id={module_sca_manifest_org.id}', f'location_id={module_location.id}', ] for pair in expected_pairs: @@ -92,7 +100,12 @@ def test_positive_org_loc_change_for_registration( # changing the org and loc to check if correct org and loc is updated on the registration command session.organization.select(org_name=new_org.name) session.location.select(loc_name=new_loc.name) - cmd = session.host.get_register_command() + cmd = session.host.get_register_command( + { + 'general.activation_keys': ak.name, + 'general.insecure': True, + } + ) expected_pairs = [ f'organization_id={new_org.id}', f'location_id={new_loc.id}', @@ -102,9 +115,7 @@ def test_positive_org_loc_change_for_registration( def test_negative_global_registration_without_ak( - module_target_sat, - module_org, - module_location, + module_target_sat, function_org, function_location ): """Attempt to register a host without ActivationKey @@ -113,8 +124,8 @@ def test_negative_global_registration_without_ak( :expectedresults: Generate command is disabled without ActivationKey """ with module_target_sat.ui_session() as session: - session.organization.select(org_name=module_org.name) - session.location.select(loc_name=module_location.name) + session.organization.select(org_name=function_org.name) + session.location.select(loc_name=function_location.name) with pytest.raises(DisabledWidgetError) as context: session.host.get_register_command() assert 'Generate registration command button is disabled' in str(context.value) @@ -125,14 +136,13 @@ def test_negative_global_registration_without_ak( @pytest.mark.tier3 @pytest.mark.rhel_ver_match('[^6]') def test_positive_global_registration_end_to_end( - session, module_activation_key, module_org, smart_proxy_location, default_os, default_smart_proxy, rhel_contenthost, - target_sat, + module_target_sat, ): """Host registration form produces a correct registration command and host is registered successfully with it, remote execution and insights are set up @@ -150,25 +160,27 @@ def test_positive_global_registration_end_to_end( """ # make sure global parameters for rex and insights are set to true insights_cp = ( - target_sat.api.CommonParameter() + module_target_sat.api.CommonParameter() .search(query={'search': 'name=host_registration_insights'})[0] .read() ) rex_cp = ( - target_sat.api.CommonParameter() + module_target_sat.api.CommonParameter() .search(query={'search': 'name=host_registration_remote_execution'})[0] .read() ) if not insights_cp.value: - target_sat.api.CommonParameter(id=insights_cp.id, value=1).update(['value']) + module_target_sat.api.CommonParameter(id=insights_cp.id, value=1).update(['value']) if not rex_cp.value: - target_sat.api.CommonParameter(id=rex_cp.id, value=1).update(['value']) + module_target_sat.api.CommonParameter(id=rex_cp.id, value=1).update(['value']) # rex interface iface = 'eth0' # fill in the global registration form - with session: + with module_target_sat.ui_session() as session: + session.organization.select(org_name=module_org.name) + session.location.select(loc_name=smart_proxy_location.name) cmd = session.host.get_register_command( { 'general.operating_system': default_os.title, @@ -219,7 +231,7 @@ def test_positive_global_registration_end_to_end( assert result.status == 0 assert datetime.now(tzinfo).strftime('%Y-%m-%d') in result.stdout # Set "Connect to host using IP address" - target_sat.api.Parameter( + module_target_sat.api.Parameter( host=rhel_contenthost.hostname, name='remote_execution_connect_by_ip', parameter_type='boolean', @@ -227,7 +239,7 @@ def test_positive_global_registration_end_to_end( ).create() # run insights-client via REX command = "insights-client --status" - invocation_command = target_sat.cli_factory.job_invocation( + invocation_command = module_target_sat.cli_factory.job_invocation( { 'job-template': 'Run Command - Script Default', 'inputs': f'command={command}', @@ -236,24 +248,24 @@ def test_positive_global_registration_end_to_end( ) # results provide all info but job invocation might not be finished yet result = ( - target_sat.api.JobInvocation() + module_target_sat.api.JobInvocation() .search( query={'search': f'id={invocation_command["id"]} and host={rhel_contenthost.hostname}'} )[0] .read() ) # make sure that task is finished - task_result = target_sat.wait_for_tasks( + task_result = module_target_sat.wait_for_tasks( search_query=(f'id = {result.task.id}'), search_rate=2, max_tries=60 ) assert task_result[0].result == 'success' host = ( - target_sat.api.Host() + module_target_sat.api.Host() .search(query={'search': f'name={rhel_contenthost.hostname}'})[0] .read() ) for interface in host.interface: - interface_result = target_sat.api.Interface(host=host.id).search( + interface_result = module_target_sat.api.Interface(host=host.id).search( query={'search': f'{interface.id}'} )[0] # more interfaces can be inside the host @@ -263,11 +275,8 @@ def test_positive_global_registration_end_to_end( @pytest.mark.tier2 def test_global_registration_form_populate( - module_org, - session, - module_ak_with_cv, - module_lce, - module_promoted_cv, + function_sca_manifest_org, + function_activation_key, default_architecture, default_os, target_sat, @@ -298,21 +307,26 @@ def test_global_registration_form_populate( group_params = {'name': 'host_packages', 'value': constants.FAKE_0_CUSTOM_PACKAGE} parent_hg = target_sat.api.HostGroup( name=hg_name, - organization=[module_org], - lifecycle_environment=module_lce, + organization=[function_sca_manifest_org], + lifecycle_environment=function_sca_manifest_org.library.id, architecture=default_architecture, operatingsystem=default_os, - content_view=module_promoted_cv, + content_view=function_sca_manifest_org.default_content_view.id, group_parameters_attributes=[group_params], ).create() target_sat.api.HostGroup(name=hg_nested_name, parent=parent_hg).create() new_org = target_sat.api.Organization().create() - new_ak = target_sat.api.ActivationKey(organization=new_org).create() - with session: + new_ak = target_sat.api.ActivationKey( + content_view=new_org.default_content_view, + environment=target_sat.api.LifecycleEnvironment(id=new_org.library.id), + organization=new_org, + ).create() + with target_sat.ui_session() as session: + session.organization.select(org_name=function_sca_manifest_org.name) session.hostgroup.update( f'{hg_name}/{hg_nested_name}', { - 'activation_keys.activation_keys': module_ak_with_cv.name, + 'activation_keys.activation_keys': function_activation_key.name, }, ) cmd = session.host.get_register_command( @@ -324,15 +338,15 @@ def test_global_registration_form_populate( full_read=True, ) assert hg_nested_name in cmd['general']['host_group'] - assert module_ak_with_cv.name in cmd['general']['activation_key_helper'] + assert function_activation_key.name in cmd['general']['activation_key_helper'] assert constants.FAKE_0_CUSTOM_PACKAGE in cmd['advanced']['install_packages_helper'] session.organization.select(org_name=new_org.name) + session.browser.refresh() cmd = session.host.get_register_command( { - 'general.organization': new_org.name, - 'general.operating_system': default_os.title, 'general.insecure': True, + 'advanced.force': True, }, full_read=True, ) @@ -341,10 +355,10 @@ def test_global_registration_form_populate( @pytest.mark.tier2 -@pytest.mark.usefixtures('enable_capsule_for_registration') +@pytest.mark.rhel_ver_match('8') @pytest.mark.no_containers def test_global_registration_with_gpg_repo_and_default_package( - session, module_activation_key, default_os, default_smart_proxy, rhel8_contenthost + module_activation_key, rhel_contenthost, target_sat, module_org ): """Host registration form produces a correct registration command and host is registered successfully with gpg repo enabled and have default package @@ -365,15 +379,14 @@ def test_global_registration_with_gpg_repo_and_default_package( :parametrized: yes """ - client = rhel8_contenthost + client = rhel_contenthost repo_name = 'foreman_register' repo_url = settings.repos.gr_yum_repo.url repo_gpg_url = settings.repos.gr_yum_repo.gpg_url - with session: + with target_sat.ui_session() as session: + session.organization.select(org_name=module_org.name) cmd = session.host.get_register_command( { - 'general.operating_system': default_os.title, - 'general.capsule': default_smart_proxy.name, 'general.activation_keys': module_activation_key.name, 'general.insecure': True, 'advanced.force': True, @@ -406,9 +419,9 @@ def test_global_registration_with_gpg_repo_and_default_package( @pytest.mark.tier3 -@pytest.mark.usefixtures('enable_capsule_for_registration') +@pytest.mark.rhel_ver_match('9') def test_global_re_registration_host_with_force_ignore_error_options( - session, module_activation_key, default_os, default_smart_proxy, rhel7_contenthost + module_activation_key, rhel_contenthost, module_target_sat, module_org ): """If the ignore_error and force checkbox is checked then registered host can get re-registered without any error. @@ -426,12 +439,11 @@ def test_global_re_registration_host_with_force_ignore_error_options( :parametrized: yes """ - client = rhel7_contenthost - with session: + client = rhel_contenthost + with module_target_sat.ui_session() as session: + session.organization.select(org_name=module_org.name) cmd = session.host.get_register_command( { - 'general.operating_system': default_os.title, - 'general.capsule': default_smart_proxy.name, 'general.activation_keys': module_activation_key.name, 'general.insecure': True, 'advanced.force': True, @@ -448,9 +460,9 @@ def test_global_re_registration_host_with_force_ignore_error_options( @pytest.mark.tier2 -@pytest.mark.usefixtures('enable_capsule_for_registration') +@pytest.mark.rhel_ver_match('8') def test_global_registration_token_restriction( - session, module_activation_key, rhel8_contenthost, default_os, default_smart_proxy, target_sat + module_activation_key, rhel_contenthost, module_target_sat, module_org ): """Global registration token should be only used for registration call, it should be restricted for any other api calls. @@ -466,12 +478,11 @@ def test_global_registration_token_restriction( :parametrized: yes """ - client = rhel8_contenthost - with session: + client = rhel_contenthost + with module_target_sat.ui_session() as session: + session.organization.select(org_name=module_org.name) cmd = session.host.get_register_command( { - 'general.operating_system': default_os.title, - 'general.capsule': default_smart_proxy.name, 'general.activation_keys': module_activation_key.name, 'general.insecure': True, } @@ -481,20 +492,21 @@ def test_global_registration_token_restriction( auth_header = re.search(pattern, cmd).group() # build curl - curl_users = f'curl -X GET -k -H {auth_header} -i {target_sat.url}/api/users/' - curl_hosts = f'curl -X GET -k -H {auth_header} -i {target_sat.url}/api/hosts/' + curl_users = f'curl -X GET -k -H {auth_header} -i {module_target_sat.url}/api/users/' + curl_hosts = f'curl -X GET -k -H {auth_header} -i {module_target_sat.url}/api/hosts/' for curl_cmd in (curl_users, curl_hosts): result = client.execute(curl_cmd) assert result.status == 0 assert 'Unable to authenticate user' in result.stdout +@pytest.mark.rhel_ver_match('8') def test_positive_host_registration_with_non_admin_user( test_name, module_sca_manifest_org, module_location, - target_sat, - rhel8_contenthost, + module_target_sat, + rhel_contenthost, module_activation_key, ): """Register hosts from a non-admin user with only register_hosts, edit_hosts @@ -506,14 +518,14 @@ def test_positive_host_registration_with_non_admin_user( """ user_password = gen_string('alpha') org = module_sca_manifest_org - role = target_sat.api.Role(organization=[org]).create() + role = module_target_sat.api.Role(organization=[org]).create() user_permissions = { 'Organization': ['view_organizations'], 'Host': ['view_hosts'], } - target_sat.api_factory.create_role_permissions(role, user_permissions) - user = target_sat.api.User( + module_target_sat.api_factory.create_role_permissions(role, user_permissions) + user = module_target_sat.api.User( role=[role], admin=False, password=user_password, @@ -522,10 +534,12 @@ def test_positive_host_registration_with_non_admin_user( default_organization=org, default_location=module_location, ).create() - role = target_sat.cli.Role.info({'name': 'Register hosts'}) - target_sat.cli.User.add_role({'id': user.id, 'role-id': role['id']}) + role = module_target_sat.cli.Role.info({'name': 'Register hosts'}) + module_target_sat.cli.User.add_role({'id': user.id, 'role-id': role['id']}) - with Session(test_name, user=user.login, password=user_password) as session: + with module_target_sat.ui_session( + test_name, user=user.login, password=user_password + ) as session: cmd = session.host_new.get_register_command( { @@ -534,17 +548,19 @@ def test_positive_host_registration_with_non_admin_user( } ) - result = rhel8_contenthost.execute(cmd) + result = rhel_contenthost.execute(cmd) assert result.status == 0, f'Failed to register host: {result.stderr}' # Verify server.hostname and server.port from subscription-manager config - assert target_sat.hostname == rhel8_contenthost.subscription_config['server']['hostname'] - assert constants.CLIENT_PORT == rhel8_contenthost.subscription_config['server']['port'] + assert ( + module_target_sat.hostname == rhel_contenthost.subscription_config['server']['hostname'] + ) + assert rhel_contenthost.subscription_config['server']['port'] == constants.CLIENT_PORT @pytest.mark.tier2 def test_positive_global_registration_form( - session, module_activation_key, module_org, smart_proxy_location, default_os, target_sat + module_activation_key, module_org, smart_proxy_location, default_os, target_sat ): """Host registration form produces a correct curl command for various inputs @@ -572,7 +588,9 @@ def test_positive_global_registration_form( organization=[module_org], location=[smart_proxy_location] ).create() iface = 'eth0' - with session: + with target_sat.ui_session() as session: + session.organization.select(org_name=module_org.name) + session.location.select(loc_name=smart_proxy_location.name) cmd = session.host.get_register_command( { 'advanced.setup_insights': 'Yes (override)' if insights_value else 'No (override)', @@ -603,10 +621,10 @@ def test_positive_global_registration_form( @pytest.mark.tier2 +@pytest.mark.rhel_ver_match('8') def test_global_registration_with_capsule_host( - session, capsule_configured, - rhel8_contenthost, + rhel_contenthost, module_org, module_location, module_product, @@ -633,7 +651,7 @@ def test_global_registration_with_capsule_host( :CaseAutomation: Automated """ - client = rhel8_contenthost + client = rhel_contenthost repo = target_sat.api.Repository( url=settings.repos.yum_1.url, content_type=REPO_TYPE['yum'], @@ -677,7 +695,7 @@ def test_global_registration_with_capsule_host( # Wait till capsule sync finishes for task in sync_status['active_sync_tasks']: target_sat.api.ForemanTask(id=task['id']).poll() - with session: + with target_sat.ui_session() as session: session.organization.select(org_name=module_org.name) session.location.select(loc_name=module_location.name) cmd = session.host.get_register_command( @@ -697,3 +715,59 @@ def test_global_registration_with_capsule_host( assert result.status == 0 assert module_lce_library.name in result.stdout assert module_org.name in result.stdout + + +@pytest.mark.tier2 +@pytest.mark.rhel_ver_match('[^6].*') +def test_subscription_manager_install_from_repository( + module_activation_key, module_os, rhel_contenthost, target_sat, module_org +): + """Host registration form produces a correct registration command and + subscription-manager can be installed from a custom repository before + registration is completed. + + :id: b7a44f32-90b2-4fd6-b65b-5a3d2a5c5deb + + :customerscenario: true + + :expectedresults: Host is successfully registered, repo is enabled + on advanced tab and subscription-manager is installed. + + :steps: + 1. Create activation-key + 2. Open the global registration form, add repo and activation key + 3. Add 'subscription-manager' to install packages field + 4. Check subscription-manager was installed from repo_name + + :parametrized: yes + + :BZ: 1923320 + """ + client = rhel_contenthost + repo_name = 'foreman_register' + rhel_ver = rhel_contenthost.os_version.major + repo_url = settings.repos.get(f'rhel{rhel_ver}_os') + if isinstance(repo_url, dict): + repo_url = repo_url['baseos'] + # Ensure subs-man is installed from repo_name by removing existing package. + result = client.execute('rpm --erase --nodeps subscription-manager') + assert result.status == 0 + with target_sat.ui_session() as session: + session.organization.select(org_name=module_org.name) + cmd = session.host.get_register_command( + { + 'general.operating_system': module_os.title, + 'general.activation_keys': module_activation_key.name, + 'general.insecure': True, + 'advanced.force': True, + 'advanced.install_packages': 'subscription-manager', + 'advanced.repository': repo_url, + } + ) + + # run curl + result = client.execute(cmd) + assert result.status == 0 + result = client.execute('yum repolist') + assert repo_name in result.stdout + assert result.status == 0 diff --git a/tests/foreman/ui/test_remoteexecution.py b/tests/foreman/ui/test_remoteexecution.py index e1e08d070e7..e34f49772af 100644 --- a/tests/foreman/ui/test_remoteexecution.py +++ b/tests/foreman/ui/test_remoteexecution.py @@ -1,4 +1,4 @@ -"""Test class for Remote Execution Management UI +"""Test class for Job Invocation procedure :Requirement: Remoteexecution @@ -11,20 +11,97 @@ :CaseImportance: High """ +from collections import OrderedDict import datetime import time +from inflection import camelize import pytest from wait_for import wait_for -from robottelo.utils.datafactory import gen_string +from robottelo.utils.datafactory import ( + gen_string, + valid_hostgroups_list_short, +) + + +@pytest.mark.tier4 +def test_positive_hostgroups_full_nested_names( + module_org, + smart_proxy_location, + target_sat, +): + """Check that full host group names are displayed when invoking a job + + :id: 2301cd1d-ed82-4168-9f9b-d1661ac8fc5b + + :steps: + + 1. Go to Monitor -> Jobs -> Run job + 2. In "Target hosts and inputs" step, choose "Host groups" targeting + + :expectedresults: Verify that in the dropdown, full hostgroup names are present, e.g. Parent/Child/Grandchild + + :parametrized: yes + + :customerscenario: true + + :BZ: 2209968 + """ + names = valid_hostgroups_list_short() + tree = OrderedDict( + { + 'parent1': {'name': names[0], 'parent': None}, + 'parent2': {'name': names[1], 'parent': None}, + 'child1a': {'name': names[2], 'parent': 'parent1'}, + 'child1b': {'name': names[3], 'parent': 'parent1'}, + 'child2': {'name': names[4], 'parent': 'parent2'}, + 'grandchild1a1': {'name': names[5], 'parent': 'child1a'}, + 'grandchild1a2': {'name': names[6], 'parent': 'child1a'}, + 'grandchild1b': {'name': names[7], 'parent': 'child1b'}, + } + ) + expected_names = [] + for identifier, data in tree.items(): + name = data['name'] + parent_name = None if data['parent'] is None else tree[data['parent']]['name'] + target_sat.cli_factory.hostgroup( + { + 'name': name, + 'parent': parent_name, + 'organization-ids': module_org.id, + 'location-ids': smart_proxy_location.id, + } + ) + expected_name = '' + current = identifier + while current: + expected_name = ( + f"{tree[current]['name']}/{expected_name}" + if expected_name + else tree[current]['name'] + ) + current = tree[current]['parent'] + # we should have something like "parent1/child1a" + expected_names.append(expected_name) + + with target_sat.ui_session() as session: + session.organization.select(module_org.name) + session.location.select(smart_proxy_location.name) + hostgroups = session.jobinvocation.read_hostgroups() + + for name in expected_names: + assert name in hostgroups -@pytest.mark.skip_if_open('BZ:2182353') @pytest.mark.rhel_ver_match('8') -@pytest.mark.tier3 -def test_positive_run_default_job_template_by_ip(session, rex_contenthost, module_org): - """Run a job template against a single host by ip +def test_positive_run_default_job_template( + session, + target_sat, + rex_contenthost, + module_org, +): + """Run a job template on a host :id: a21eac46-1a22-472d-b4ce-66097159a868 @@ -32,38 +109,38 @@ def test_positive_run_default_job_template_by_ip(session, rex_contenthost, modul :steps: - 1. Navigate to an individual host and click Run Job - 2. Select the job and appropriate template - 3. Run the job + 1. Get contenthost with rex enabled + 2. Navigate to an individual host and click Run Job + 3. Select the job and appropriate template + 4. Run the job - :expectedresults: Verify the job was successfully ran against the host + :expectedresults: Verify the job was successfully ran against the host, check also using the job widget on the main dashboard :parametrized: yes - :bz: 1898656 + :bz: 1898656, 2182353 :customerscenario: true """ + hostname = rex_contenthost.hostname - with session: + + with target_sat.ui_session() as session: session.organization.select(module_org.name) - session.location.select('Default Location') assert session.host.search(hostname)[0]['Name'] == hostname command = 'ls' - job_status = session.host.schedule_remote_job( - [hostname], + session.jobinvocation.run( { 'category_and_template.job_category': 'Commands', 'category_and_template.job_template': 'Run Command - Script Default', + 'target_hosts_and_inputs.targetting_type': 'Hosts', + 'target_hosts_and_inputs.targets': hostname, 'target_hosts_and_inputs.command': command, - 'advanced_fields.execution_order_randomized': True, - 'schedule.immediate': True, - }, + } ) - assert job_status['overview']['job_status'] == 'Success' - assert job_status['overview']['execution_order'] == 'Execution order: randomized' - assert job_status['overview']['hosts_table'][0]['Host'] == hostname - assert job_status['overview']['hosts_table'][0]['Status'] == 'success' + session.jobinvocation.wait_job_invocation_state(entity_name='Run ls', host_name=hostname) + status = session.jobinvocation.read(entity_name='Run ls', host_name=hostname) + assert status['overview']['hosts_table'][0]['Status'] == 'success' # check status also on the job dashboard job_name = f'Run {command}' @@ -73,16 +150,54 @@ def test_positive_run_default_job_template_by_ip(session, rex_contenthost, modul assert job_name in [job['Name'] for job in success_jobs] -@pytest.mark.skip_if_open('BZ:2182353') +@pytest.mark.tier4 +@pytest.mark.rhel_ver_match('8') +def test_rex_through_host_details(session, target_sat, rex_contenthost, module_org): + """Run remote execution using the new host details page + + :id: ee625595-4995-43b2-9e6d-633c9b33ff93 + + :steps: + 1. Navigate to Overview tab + 2. Schedule a job + 3. Wait for the job to finish + 4. Job is visible in Recent jobs card + + :expectedresults: Remote execution succeeded and the job is visible on Recent jobs card on + Overview tab + """ + + hostname = rex_contenthost.hostname + + job_args = { + 'category_and_template.job_category': 'Commands', + 'category_and_template.job_template': 'Run Command - Script Default', + 'target_hosts_and_inputs.command': 'ls', + } + with target_sat.ui_session() as session: + session.organization.select(module_org.name) + session.host_new.schedule_job(hostname, job_args) + task_result = target_sat.wait_for_tasks( + search_query=(f'Remote action: Run ls on {hostname}'), + search_rate=2, + max_tries=30, + ) + task_status = target_sat.api.ForemanTask(id=task_result[0].id).poll() + assert task_status['result'] == 'success' + recent_jobs = session.host_new.get_details(hostname, "overview.recent_jobs")['overview'] + assert recent_jobs['recent_jobs']['finished']['table'][0]['column0'] == "Run ls" + assert recent_jobs['recent_jobs']['finished']['table'][0]['column2'] == "succeeded" + + +@pytest.mark.tier4 @pytest.mark.rhel_ver_match('8') -@pytest.mark.tier3 @pytest.mark.parametrize( 'ui_user', [{'admin': True}, {'admin': False}], indirect=True, ids=['adminuser', 'nonadminuser'] ) -def test_positive_run_custom_job_template_by_ip( - session, module_org, target_sat, default_location, ui_user, rex_contenthost +def test_positive_run_custom_job_template( + session, module_org, default_location, target_sat, ui_user, rex_contenthost ): - """Run a job template on a host connected by ip + """Run a job template on a host :id: 3a59eb15-67c4-46e1-ba5f-203496ec0b0c @@ -103,13 +218,14 @@ def test_positive_run_custom_job_template_by_ip( :customerscenario: true """ + + hostname = rex_contenthost.hostname ui_user.location.append(target_sat.api.Location(id=default_location.id)) ui_user.update(['location']) - hostname = rex_contenthost.hostname job_template_name = gen_string('alpha') - with session: + with target_sat.ui_session() as session: session.organization.select(module_org.name) - session.location.select('Default Location') + assert session.host.search(hostname)[0]['Name'] == hostname session.jobtemplate.create( { 'template.name': job_template_name, @@ -120,29 +236,29 @@ def test_positive_run_custom_job_template_by_ip( } ) assert session.jobtemplate.search(job_template_name)[0]['Name'] == job_template_name - assert session.host.search(hostname)[0]['Name'] == hostname - job_status = session.host.schedule_remote_job( - [hostname], + session.jobinvocation.run( { 'category_and_template.job_category': 'Miscellaneous', 'category_and_template.job_template': job_template_name, + 'target_hosts_and_inputs.targets': hostname, 'target_hosts_and_inputs.command': 'ls', - 'schedule.immediate': True, - }, + } ) - assert job_status['overview']['job_status'] == 'Success' - assert job_status['overview']['hosts_table'][0]['Host'] == hostname - assert job_status['overview']['hosts_table'][0]['Status'] == 'success' + job_description = f'{camelize(job_template_name.lower())} with inputs command="ls"' + session.jobinvocation.wait_job_invocation_state( + entity_name=job_description, host_name=hostname + ) + status = session.jobinvocation.read(entity_name=job_description, host_name=hostname) + assert status['overview']['hosts_table'][0]['Status'] == 'success' -@pytest.mark.skip_if_open('BZ:2182353') @pytest.mark.upgrade @pytest.mark.tier3 @pytest.mark.rhel_ver_list([8]) -def test_positive_run_job_template_multiple_hosts_by_ip( - session, module_org, target_sat, registered_hosts +def test_positive_run_job_template_multiple_hosts( + session, module_org, target_sat, rex_contenthosts ): - """Run a job template against multiple hosts by ip + """Run a job template against multiple hosts :id: c4439ec0-bb80-47f6-bc31-fa7193bfbeeb @@ -158,22 +274,22 @@ def test_positive_run_job_template_multiple_hosts_by_ip( :expectedresults: Verify the job was successfully ran against the hosts """ + host_names = [] - for vm in registered_hosts: + for vm in rex_contenthosts: host_names.append(vm.hostname) vm.configure_rex(satellite=target_sat, org=module_org) - with session: + with target_sat.ui_session() as session: session.organization.select(module_org.name) - session.location.select('Default Location') - hosts = session.host.search(' or '.join([f'name="{hostname}"' for hostname in host_names])) - assert {host['Name'] for host in hosts} == set(host_names) + for host in host_names: + assert session.host.search(host)[0]['Name'] == host + session.host.reset_search() job_status = session.host.schedule_remote_job( host_names, { 'category_and_template.job_category': 'Commands', 'category_and_template.job_template': 'Run Command - Script Default', - 'target_hosts_and_inputs.command': 'ls', - 'schedule.immediate': True, + 'target_hosts_and_inputs.command': 'sleep 5', }, ) assert job_status['overview']['job_status'] == 'Success' @@ -185,7 +301,6 @@ def test_positive_run_job_template_multiple_hosts_by_ip( ) -@pytest.mark.skip_if_open('BZ:2182353') @pytest.mark.rhel_ver_match('8') @pytest.mark.tier3 def test_positive_run_scheduled_job_template_by_ip(session, module_org, rex_contenthost): @@ -211,19 +326,20 @@ def test_positive_run_scheduled_job_template_by_ip(session, module_org, rex_cont :parametrized: yes """ - job_time = 10 * 60 + job_time = 6 * 60 hostname = rex_contenthost.hostname with session: session.organization.select(module_org.name) session.location.select('Default Location') assert session.host.search(hostname)[0]['Name'] == hostname plan_time = session.browser.get_client_datetime() + datetime.timedelta(seconds=job_time) + command_to_run = 'sleep 10' job_status = session.host.schedule_remote_job( [hostname], { 'category_and_template.job_category': 'Commands', 'category_and_template.job_template': 'Run Command - Script Default', - 'target_hosts_and_inputs.command': 'ls', + 'target_hosts_and_inputs.command': command_to_run, 'schedule.future': True, 'schedule_future_execution.start_at_date': plan_time.strftime("%Y/%m/%d"), 'schedule_future_execution.start_at_time': plan_time.strftime("%H:%M"), @@ -237,297 +353,36 @@ def test_positive_run_scheduled_job_template_by_ip(session, module_org, rex_cont # the job_time must be significantly greater than job creation time. assert job_left_time > 0 assert job_status['overview']['hosts_table'][0]['Host'] == hostname - assert job_status['overview']['hosts_table'][0]['Status'] == 'N/A' + assert job_status['overview']['hosts_table'][0]['Status'] in ('Awaiting start', 'N/A') # sleep 3/4 of the left time time.sleep(job_left_time * 3 / 4) - job_status = session.jobinvocation.read('Run ls', hostname, 'overview.hosts_table') + job_status = session.jobinvocation.read( + f'Run {command_to_run}', hostname, 'overview.hosts_table' + ) assert job_status['overview']['hosts_table'][0]['Host'] == hostname - assert job_status['overview']['hosts_table'][0]['Status'] == 'N/A' + assert job_status['overview']['hosts_table'][0]['Status'] in ('Awaiting start', 'N/A') # recalculate the job left time to be more accurate job_left_time = (plan_time - session.browser.get_client_datetime()).total_seconds() # the last read time should not take more than 1/4 of the last left time assert job_left_time > 0 wait_for( - lambda: session.jobinvocation.read('Run ls', hostname, 'overview.hosts_table')[ - 'overview' - ]['hosts_table'][0]['Status'] + lambda: session.jobinvocation.read( + f'Run {command_to_run}', hostname, 'overview.hosts_table' + )['overview']['hosts_table'][0]['Status'] == 'running', timeout=(job_left_time + 30), delay=1, ) # wait the job to change status to "success" wait_for( - lambda: session.jobinvocation.read('Run ls', hostname, 'overview.hosts_table')[ - 'overview' - ]['hosts_table'][0]['Status'] + lambda: session.jobinvocation.read( + f'Run {command_to_run}', hostname, 'overview.hosts_table' + )['overview']['hosts_table'][0]['Status'] == 'success', timeout=30, delay=1, ) - job_status = session.jobinvocation.read('Run ls', hostname, 'overview') + job_status = session.jobinvocation.read(f'Run {command_to_run}', hostname, 'overview') assert job_status['overview']['job_status'] == 'Success' assert job_status['overview']['hosts_table'][0]['Host'] == hostname assert job_status['overview']['hosts_table'][0]['Status'] == 'success' - - -@pytest.mark.stubbed -@pytest.mark.tier3 -def test_positive_ansible_job_check_mode(session): - """Run a job on a host with enable_roles_check_mode parameter enabled - - :id: 7aeb7253-e555-4e28-977f-71f16d3c32e2 - - :steps: - - 1. Set the value of the ansible_roles_check_mode parameter to true on a host - 2. Associate one or more Ansible roles with the host - 3. Run Ansible roles against the host - - :expectedresults: Verify that the roles were run in check mode - (i.e. no changes were made on the host) - - :CaseAutomation: NotAutomated - - :CaseComponent: Ansible - - :Team: Rocket - """ - - -@pytest.mark.stubbed -@pytest.mark.tier3 -def test_positive_ansible_config_report_failed_tasks_errors(session): - """Check that failed Ansible tasks show as errors in the config report - - :id: 1a91e534-143f-4f35-953a-7ad8b7d2ddf3 - - :steps: - - 1. Import Ansible roles - 2. Assign Ansible roles to a host - 3. Run Ansible roles on host - - :expectedresults: Verify that any task failures are listed as errors in the config report - - :CaseAutomation: NotAutomated - - :CaseComponent: Ansible - - :Team: Rocket - """ - - -@pytest.mark.stubbed -@pytest.mark.tier3 -def test_positive_ansible_config_report_changes_notice(session): - """Check that Ansible tasks that make changes on a host show as notice in the config report - - :id: 8c90f179-8b70-4932-a477-75dc3566c437 - - :steps: - - 1. Import Ansible Roles - 2. Assign Ansible roles to a host - 3. Run Ansible Roles on a host - - :expectedresults: Verify that any tasks that make changes on the host - are listed as notice in the config report - - :CaseAutomation: NotAutomated - - :CaseComponent: Ansible - - :Team: Rocket - """ - - -@pytest.mark.stubbed -@pytest.mark.tier3 -def test_positive_ansible_variables_imported_with_roles(session): - """Verify that, when Ansible roles are imported, their variables are imported simultaneously - - :id: 107c53e8-5a8a-4291-bbde-fbd66a0bb85e - - :steps: - - 1. Import Ansible roles - - :expectedresults: Verify that any variables in the role were also imported to Satellite - - :CaseAutomation: NotAutomated - - :CaseComponent: Ansible - - :Team: Rocket - """ - - -@pytest.mark.stubbed -@pytest.mark.tier3 -def test_positive_roles_import_in_background(session): - """Verify that importing roles does not create a popup that blocks the UI - - :id: 4f1c7b76-9c67-42b2-9a73-980ca1f05abc - - :steps: - - 1. Import Ansible roles - - :expectedresults: Verify that the UI is accessible while roles are importing - - :CaseAutomation: NotAutomated - - :CaseComponent: Ansible - - :Team: Rocket - """ - - -@pytest.mark.stubbed -@pytest.mark.tier3 -def test_positive_ansible_roles_ignore_list(session): - """Verify that the ignore list setting prevents selected roles from being available for import - - :id: 6fa1d8f0-b583-4a07-88eb-c9ae7fcd0219 - - :steps: - - 1. Add roles to the ignore list in Administer > Settings > Ansible - 2. Navigate to Configure > Roles - - :expectedresults: Verify that any roles on the ignore list are not available for import - - :CaseAutomation: NotAutomated - - :CaseComponent: Ansible - - :Team: Rocket - """ - - -@pytest.mark.stubbed -@pytest.mark.tier3 -def test_positive_ansible_variables_installed_with_collection(session): - """Verify that installing an Ansible collection also imports - any variables associated with the collection - - :id: 7ff88022-fe9b-482f-a6bb-3922036a1e1c - - :steps: - - 1. Install an Ansible collection - 2. Navigate to Configure > Variables - - :expectedresults: Verify that any variables associated with the collection - are present on Configure > Variables - - :CaseAutomation: NotAutomated - - :CaseComponent: Ansible - - :Team: Rocket - """ - - -@pytest.mark.stubbed -@pytest.mark.tier3 -def test_positive_install_ansible_collection_via_job_invocation(session): - """Verify that Ansible collections can be installed on hosts via job invocations - - :id: d4096aef-f6fc-41b6-ae56-d19b1f49cd42 - - :steps: - - 1. Enable a host for remote execution - 2. Navigate to Hosts > Schedule Remote Job - 3. Select "Ansible Galaxy" as the job category - 4. Select "Ansible Collection - Install from Galaxy" as the job template - 5. Enter a collection in the ansible_collections_list field - 6. Click "Submit" - - :expectedresults: The Ansible collection is successfully installed on the host - - :CaseAutomation: NotAutomated - - :CaseComponent: Ansible - - :Team: Rocket - """ - - -@pytest.mark.stubbed -@pytest.mark.tier3 -def test_positive_set_ansible_role_order_per_host(session): - """Verify that role run order can be set and that this order is respected when roles are run - - :id: 24fbcd60-7cd1-46ff-86ac-16d6b436202c - - :steps: - - 1. Enable a host for remote execution - 2. Navigate to Hosts > All Hosts > $hostname > Edit > Ansible Roles - 3. Assign more than one role to the host - 4. Use the drag-and-drop mechanism to change the order of the roles - 5. Run Ansible roles on the host - - :expectedresults: The roles are run in the specified order - - :CaseAutomation: NotAutomated - - :CaseComponent: Ansible - - :Team: Rocket - """ - - -@pytest.mark.stubbed -@pytest.mark.tier3 -def test_positive_set_ansible_role_order_per_hostgroup(session): - """Verify that role run order can be set and that this order is respected when roles are run - - :id: 9eb5bc8e-081a-45b9-8751-f4220c944da6 - - :steps: - - 1. Enable a host for remote execution - 2. Create a host group - 3. Navigate to Configure > Host Groups > $hostgroup > Ansible Roles - 4. Assign more than one role to the host group - 5. Use the drag-and-drop mechanism to change the order of the roles - 6. Add the host to the host group - 7. Run Ansible roles on the host group - - :expectedresults: The roles are run in the specified order - - :CaseAutomation: NotAutomated - - :CaseComponent: Ansible - - :Team: Rocket - """ - - -@pytest.mark.stubbed -@pytest.mark.tier3 -def test_positive_matcher_field_highlight(session): - """Verify that Ansible variable matcher fields change color when modified - - :id: 67b45cfe-31bb-41a8-b88e-27917c68f33e - - :steps: - - 1. Navigate to Configure > Variables > $variablename - 2. Select the "Override" checkbox in the "Default Behavior" section - 3. Click "+Add Matcher" in the "Specify Matcher" section - 4. Select an option from the "Attribute type" dropdown - 5. Add text to the attribute type input field - 6. Add text to the "Value" input field - - :expectedresults: The background of each field turns yellow when a change is made - - :CaseAutomation: NotAutomated - - :CaseComponent: Ansible - - :Team: Rocket - """ diff --git a/tests/foreman/ui/test_repository.py b/tests/foreman/ui/test_repository.py index 1d9d40e10bb..b8ac36f6449 100644 --- a/tests/foreman/ui/test_repository.py +++ b/tests/foreman/ui/test_repository.py @@ -831,7 +831,7 @@ def test_positive_delete_random_docker_repo(session, module_org, module_target_s @pytest.mark.tier2 -def test_positive_delete_rhel_repo(session, module_entitlement_manifest_org, target_sat): +def test_positive_delete_rhel_repo(session, module_sca_manifest_org, target_sat): """Enable and sync a Red Hat Repository, and then delete it :id: e96f369d-3e58-4824-802e-0b7e99d6d207 @@ -847,7 +847,7 @@ def test_positive_delete_rhel_repo(session, module_entitlement_manifest_org, tar repository_name = sat_tools_repo.data['repository'] product_name = sat_tools_repo.data['product'] with session: - session.organization.select(module_entitlement_manifest_org.name) + session.organization.select(module_sca_manifest_org.name) session.redhatrepository.enable( sat_tools_repo.data['repository-set'], sat_tools_repo.data['arch'], diff --git a/tests/foreman/ui/test_rhc.py b/tests/foreman/ui/test_rhc.py index 0fdefee5bf1..df90a5768ca 100644 --- a/tests/foreman/ui/test_rhc.py +++ b/tests/foreman/ui/test_rhc.py @@ -4,9 +4,9 @@ :CaseAutomation: Automated -:CaseComponent: RHCloud-CloudConnector +:CaseComponent: RHCloud -:Team: Platform +:Team: Phoenix-subscriptions :CaseImportance: High diff --git a/tests/foreman/ui/test_rhcloud_insights.py b/tests/foreman/ui/test_rhcloud_insights.py index 9dcd309055c..3646658c2b0 100644 --- a/tests/foreman/ui/test_rhcloud_insights.py +++ b/tests/foreman/ui/test_rhcloud_insights.py @@ -1,12 +1,12 @@ """Tests for RH Cloud - Inventory -:Requirement: RH Cloud - Inventory +:Requirement: RHCloud :CaseAutomation: Automated -:CaseComponent: RHCloud-Inventory +:CaseComponent: RHCloud -:Team: Platform +:Team: Phoenix-subscriptions :CaseImportance: High diff --git a/tests/foreman/ui/test_rhcloud_inventory.py b/tests/foreman/ui/test_rhcloud_inventory.py index e242366bf0a..9c98ade0205 100644 --- a/tests/foreman/ui/test_rhcloud_inventory.py +++ b/tests/foreman/ui/test_rhcloud_inventory.py @@ -1,12 +1,12 @@ """Tests for RH Cloud - Inventory, also known as Insights Inventory Upload -:Requirement: RH Cloud - Inventory +:Requirement: RHCloud :CaseAutomation: Automated -:CaseComponent: RHCloud-Inventory +:CaseComponent: RHCloud -:Team: Platform +:Team: Phoenix-subscriptions :CaseImportance: High diff --git a/tests/foreman/ui/test_subscription.py b/tests/foreman/ui/test_subscription.py index 9981e371cd8..45bfcd47d91 100644 --- a/tests/foreman/ui/test_subscription.py +++ b/tests/foreman/ui/test_subscription.py @@ -93,9 +93,8 @@ def test_positive_end_to_end(session, target_sat): ] org = target_sat.api.Organization().create() _, temporary_local_manifest_path = mkstemp(prefix='manifest-', suffix='.zip') - with clone() as manifest: - with open(temporary_local_manifest_path, 'wb') as file_handler: - file_handler.write(manifest.content.read()) + with clone() as manifest, open(temporary_local_manifest_path, 'wb') as file_handler: + file_handler.write(manifest.content.read()) with session: session.organization.select(org.name) # Ignore "Danger alert: Katello::Errors::UpstreamConsumerNotFound'" as server will connect diff --git a/tests/foreman/ui/test_sync.py b/tests/foreman/ui/test_sync.py index d4351bc4923..44cfc7483b8 100644 --- a/tests/foreman/ui/test_sync.py +++ b/tests/foreman/ui/test_sync.py @@ -40,7 +40,7 @@ def module_custom_product(module_org, module_target_sat): @pytest.mark.skip_if_not_set('fake_manifest') @pytest.mark.tier2 @pytest.mark.upgrade -def test_positive_sync_rh_repos(session, target_sat, module_entitlement_manifest_org): +def test_positive_sync_rh_repos(session, target_sat, module_sca_manifest_org): """Create Content RedHat Sync with two repos. :id: e30f6509-0b65-4bcc-a522-b4f3089d3911 @@ -57,7 +57,7 @@ def test_positive_sync_rh_repos(session, target_sat, module_entitlement_manifest for distro, repo in zip(distros, repos, strict=True) ] for repo_collection in repo_collections: - repo_collection.setup(module_entitlement_manifest_org.id, synchronize=False) + repo_collection.setup(module_sca_manifest_org.id, synchronize=False) repo_paths = [ ( repo.repo_data['product'], @@ -68,7 +68,7 @@ def test_positive_sync_rh_repos(session, target_sat, module_entitlement_manifest for repo in repos ] with session: - session.organization.select(org_name=module_entitlement_manifest_org.name) + session.organization.select(org_name=module_sca_manifest_org.name) results = session.sync_status.synchronize(repo_paths) assert len(results) == len(repo_paths) assert all([result == 'Syncing Complete.' for result in results]) diff --git a/tests/foreman/virtwho/ui/test_esx_sca.py b/tests/foreman/virtwho/ui/test_esx_sca.py index a4c45dd4960..3656ba09038 100644 --- a/tests/foreman/virtwho/ui/test_esx_sca.py +++ b/tests/foreman/virtwho/ui/test_esx_sca.py @@ -243,7 +243,12 @@ def test_positive_filtering_option( @pytest.mark.tier2 def test_positive_last_checkin_status( - self, module_sca_manifest_org, virtwho_config_ui, form_data_ui, org_session + self, + module_sca_manifest_org, + virtwho_config_ui, + form_data_ui, + org_session, + default_location, ): """Verify the Last Checkin status on Content Hosts Page. @@ -265,6 +270,7 @@ def test_positive_last_checkin_status( ) time_now = org_session.browser.get_client_datetime() assert org_session.virtwho_configure.search(name)[0]['Status'] == 'ok' + org_session.location.select(default_location.name) checkin_time = org_session.contenthost.search(hypervisor_name)[0]['Last Checkin'] # 10 mins margin to check the Last Checkin time assert ( diff --git a/tests/robottelo/conftest.py b/tests/robottelo/conftest.py index d8ffb1ad75b..dbd1fe41b50 100644 --- a/tests/robottelo/conftest.py +++ b/tests/robottelo/conftest.py @@ -1,3 +1,4 @@ +import contextlib import glob import os from pathlib import Path @@ -44,8 +45,6 @@ def exec_test(request, dummy_test): yield report_file for logfile in glob.glob('robottelo*.log'): os.remove(logfile) - try: - os.remove(report_file) - except OSError: + with contextlib.suppress(OSError): # the file might not exist if the test fails prematurely - pass + os.remove(report_file) diff --git a/tests/robottelo/test_cli.py b/tests/robottelo/test_cli.py index c6e8bffd846..e6d6de2dd7b 100644 --- a/tests/robottelo/test_cli.py +++ b/tests/robottelo/test_cli.py @@ -146,9 +146,9 @@ def assert_response_error(self, expected_error, stderr='some error'): def test_add_operating_system(self, construct, execute): """Check command_sub edited when executing add_operating_system""" options = {'foo': 'bar'} - assert 'add-operatingsystem' != Base.command_sub + assert Base.command_sub != 'add-operatingsystem' assert execute.return_value == Base.add_operating_system(options) - assert 'add-operatingsystem' == Base.command_sub + assert Base.command_sub == 'add-operatingsystem' construct.assert_called_once_with(options) execute.assert_called_once_with(construct.return_value) @@ -158,7 +158,7 @@ def test_add_create_with_empty_result(self, construct, execute): """Check command create when result is empty""" execute.return_value = [] assert execute.return_value == Base.create() - assert 'create' == Base.command_sub + assert Base.command_sub == 'create' construct.assert_called_once_with({}) execute.assert_called_once_with(construct.return_value, output_format='csv', timeout=None) @@ -169,7 +169,7 @@ def test_add_create_with_result_dct_without_id(self, construct, execute, info): """Check command create when result has dct but dct hasn't id key""" execute.return_value = [{'not_id': 'foo'}] assert execute.return_value == Base.create() - assert 'create' == Base.command_sub + assert Base.command_sub == 'create' construct.assert_called_once_with({}) execute.assert_called_once_with(construct.return_value, output_format='csv', timeout=None) assert not info.called @@ -184,7 +184,7 @@ def test_add_create_with_result_dct_with_id_not_required_org(self, construct, ex execute.return_value = [{'id': 'foo', 'bar': 'bas'}] Base.command_requires_org = False assert execute.return_value == Base.create() - assert 'create' == Base.command_sub + assert Base.command_sub == 'create' construct.assert_called_once_with({}) execute.assert_called_once_with(construct.return_value, output_format='csv', timeout=None) info.assert_called_once_with({'id': 'foo'}) @@ -199,7 +199,7 @@ def test_add_create_with_result_dct_with_id_required_org(self, construct, execut execute.return_value = [{'id': 'foo', 'bar': 'bas'}] Base.command_requires_org = True assert execute.return_value == Base.create({'organization-id': 'org-id'}) - assert 'create' == Base.command_sub + assert Base.command_sub == 'create' construct.assert_called_once_with({'organization-id': 'org-id'}) execute.assert_called_once_with(construct.return_value, output_format='csv', timeout=None) info.assert_called_once_with({'id': 'foo', 'organization-id': 'org-id'}) @@ -214,7 +214,7 @@ def test_add_create_with_result_dct_id_required_org_error(self, construct, execu Base.command_requires_org = True with pytest.raises(CLIError): Base.create() - assert 'create' == Base.command_sub + assert Base.command_sub == 'create' construct.assert_called_once_with({}) execute.assert_called_once_with(construct.return_value, output_format='csv', timeout=None) @@ -258,7 +258,7 @@ def test_execute_with_raw_response(self, settings, command): response = Base.execute('some_cmd', return_raw_response=True) ssh_cmd = 'LANG=en_US hammer -v -u admin -p password some_cmd' command.assert_called_once_with( - ssh_cmd.encode('utf-8'), + ssh_cmd, hostname=mock.ANY, output_format=None, timeout=None, @@ -277,7 +277,7 @@ def test_execute_with_performance(self, settings, command, handle_resp): response = Base.execute('some_cmd', hostname=None, output_format='json') ssh_cmd = 'LANG=en_US time -p hammer -v -u admin -p password --output=json some_cmd' command.assert_called_once_with( - ssh_cmd.encode('utf-8'), + ssh_cmd, hostname=mock.ANY, output_format='json', timeout=None, @@ -300,7 +300,7 @@ def test_exists_with_option_and_no_empty_return(self, lst_method): my_options = {'search': 'foo=bar'} response = Base.exists(my_options, search=['id', 1]) lst_method.assert_called_once_with(my_options) - assert 1 == response + assert response == 1 @mock.patch('robottelo.cli.base.Base.command_requires_org') def test_info_requires_organization_id(self, _): # noqa: PT019 - not a fixture @@ -366,7 +366,7 @@ def test_info_parsing_response(self, construct, execute, parse): def test_list_with_default_per_page(self, construct, execute): """Check list method set per_page as 1000 by default""" assert execute.return_value == Base.list(options={'organization-id': 1}) - assert 'list' == Base.command_sub + assert Base.command_sub == 'list' construct.assert_called_once_with({'organization-id': 1, 'per-page': 10000}) execute.assert_called_once_with(construct.return_value, output_format='csv') diff --git a/tests/robottelo/test_decorators.py b/tests/robottelo/test_decorators.py index 0f8ac441ce9..6c5e60f568a 100644 --- a/tests/robottelo/test_decorators.py +++ b/tests/robottelo/test_decorators.py @@ -28,12 +28,12 @@ def test_create_and_not_add_to_cache(self, make_foo): """ make_foo(cached=False) assert 'foo' not in decorators.OBJECT_CACHE - assert decorators.OBJECT_CACHE == {} + assert {} == decorators.OBJECT_CACHE def test_build_cache(self, make_foo): """Create a new object and add it to the cache.""" obj = make_foo(cached=True) - assert decorators.OBJECT_CACHE == {'foo': {'id': 42}} + assert {'foo': {'id': 42}} == decorators.OBJECT_CACHE assert id(decorators.OBJECT_CACHE['foo']) == id(obj) def test_return_from_cache(self, make_foo): diff --git a/tests/robottelo/test_dependencies.py b/tests/robottelo/test_dependencies.py index 185f0866737..950cbeebec4 100644 --- a/tests/robottelo/test_dependencies.py +++ b/tests/robottelo/test_dependencies.py @@ -1,4 +1,5 @@ """Test important behavior in robottelo's direct dependencies""" +import contextlib def test_cryptography(): @@ -81,7 +82,7 @@ def test_productmd(): def test_pyotp(): import pyotp - fake_secret = 'JBSWY3DPEHPK3PXP' + fake_secret = 'JBSWY3DPEHPK3PXP' # notsecret totp = pyotp.TOTP(fake_secret) assert totp.now() @@ -115,10 +116,8 @@ def test_tenacity(): def test(): raise Exception('test') - try: + with contextlib.suppress(Exception): test() - except Exception: - pass def test_testimony(): diff --git a/tests/robottelo/test_func_locker.py b/tests/robottelo/test_func_locker.py index ad4ab5c74aa..d0da97431e0 100644 --- a/tests/robottelo/test_func_locker.py +++ b/tests/robottelo/test_func_locker.py @@ -35,9 +35,7 @@ def __init__(self): def read(self): with open(self.file_name) as cf: - content = cf.read() - - return content + return cf.read() def write(self, content): with open(self.file_name, 'wb') as cf: @@ -98,9 +96,10 @@ def simple_recursive_locking_function(): """try to trigger the same lock from the same process, an exception should be expected """ - with func_locker.locking_function(simple_locked_function): - with func_locker.locking_function(simple_locked_function): - pass + with func_locker.locking_function(simple_locked_function), func_locker.locking_function( + simple_locked_function + ): + pass return 'I should not be reached' @@ -126,9 +125,10 @@ def simple_function_to_lock(): def simple_with_locking_function(index=None): global counter_file time.sleep(0.05) - with func_locker.locking_function(simple_locked_function): - with open(_get_function_lock_path('simple_locked_function')) as rf: - content = rf.read() + with func_locker.locking_function(simple_locked_function), open( + _get_function_lock_path('simple_locked_function') + ) as rf: + content = rf.read() if index is not None: saved_counter = int(counter_file.read()) @@ -169,7 +169,7 @@ def simple_scoped_lock_function(): """This function do nothing, when called the lock function must create a lock file """ - return None + return @func_locker.lock_function @@ -182,14 +182,14 @@ def simple_scoped_locking_function(): ): pass - return None + return def simple_function_not_locked(): """This function do nothing, when called with locking, exception must be raised that this function is not locked """ - return None + return class TestFuncLocker: @@ -234,9 +234,10 @@ def test_locker_file_location_when_in_class(self): content = '' assert str(os.getpid()) != content - with func_locker.locking_function(SimpleClass.simple_function_to_lock): - with open(file_path) as rf: - content = rf.read() + with func_locker.locking_function(SimpleClass.simple_function_to_lock), open( + file_path + ) as rf: + content = rf.read() assert str(os.getpid()) == content @@ -248,9 +249,10 @@ def test_locker_file_location_when_in_class(self): content = '' assert str(os.getpid()) != content - with func_locker.locking_function(SimpleClass.simple_function_to_lock_cls): - with open(file_path) as rf: - content = rf.read() + with func_locker.locking_function(SimpleClass.simple_function_to_lock_cls), open( + file_path + ) as rf: + content = rf.read() assert str(os.getpid()) == content @@ -294,9 +296,10 @@ def test_locker_file_location_when_in_class(self): else: content = '' assert str(os.getpid()) != content - with func_locker.locking_function(SimpleClass.SubClass.simple_function_to_lock_cls): - with open(file_path) as rf: - content = rf.read() + with func_locker.locking_function(SimpleClass.SubClass.simple_function_to_lock_cls), open( + file_path + ) as rf: + content = rf.read() assert str(os.getpid()) == content @@ -407,7 +410,7 @@ def test_scoped_with_locking(self): assert os.path.exists(lock_file_path) def test_negative_with_locking_not_locked(self): - - with pytest.raises(func_locker.FunctionLockerError, match=r'.*Cannot ensure locking.*'): - with func_locker.locking_function(simple_function_not_locked): - pass + with pytest.raises( + func_locker.FunctionLockerError, match=r'.*Cannot ensure locking.*' + ), func_locker.locking_function(simple_function_not_locked): + pass diff --git a/tests/robottelo/test_issue_handlers.py b/tests/robottelo/test_issue_handlers.py index 4da0b6df011..d8fdce0c7b0 100644 --- a/tests/robottelo/test_issue_handlers.py +++ b/tests/robottelo/test_issue_handlers.py @@ -342,10 +342,16 @@ def test_bz_should_not_deselect(self): @pytest.mark.parametrize('issue', ["BZ123456", "XX:123456", "KK:89456", "123456", 999999]) def test_invalid_handler(self, issue): """Assert is_open w/ invalid handlers raise AttributeError""" - issue_deselect = should_deselect(issue) - with pytest.raises(AttributeError): - is_open(issue) - assert issue_deselect is None + if issue == 'BZ123456': + with pytest.raises(KeyError): + should_deselect(issue) + with pytest.raises(KeyError): + is_open(issue) + else: + issue_deselect = should_deselect(issue) + with pytest.raises(AttributeError): + is_open(issue) + assert issue_deselect is None def test_bz_cache(self, request): """Assert basic behavior of the --bz-cache pytest option""" diff --git a/tests/upgrades/conftest.py b/tests/upgrades/conftest.py index d75929cb7b4..4c759f66b72 100644 --- a/tests/upgrades/conftest.py +++ b/tests/upgrades/conftest.py @@ -154,8 +154,7 @@ def get_entity_data(scenario_name): """ with open('scenario_entities') as pref: entity_data = json.load(pref) - entity_data = entity_data.get(scenario_name) - return entity_data + return entity_data.get(scenario_name) def get_all_entity_data(): @@ -171,8 +170,7 @@ def get_all_entity_data(): with scenario_name as keys and corresponding attribute data as values. """ with open('scenario_entities') as pref: - entity_data = json.load(pref) - return entity_data + return json.load(pref) def _read_test_data(test_node_id): @@ -334,17 +332,18 @@ def __initiate(config): global POST_UPGRADE global PRE_UPGRADE_TESTS_FILE_PATH PRE_UPGRADE_TESTS_FILE_PATH = getattr(config.option, PRE_UPGRADE_TESTS_FILE_OPTION) - if not [ - upgrade_mark - for upgrade_mark in (PRE_UPGRADE_MARK, POST_UPGRADE_MARK) - if upgrade_mark in config.option.markexpr - ]: - # Raise only if the `tests/upgrades` directory is selected - if 'upgrades' in config.args[0]: - pytest.fail( - f'For upgrade scenarios either {PRE_UPGRADE_MARK} or {POST_UPGRADE_MARK} mark ' - 'must be provided' - ) + if ( + not [ + upgrade_mark + for upgrade_mark in (PRE_UPGRADE_MARK, POST_UPGRADE_MARK) + if upgrade_mark in config.option.markexpr + ] + and 'upgrades' in config.args[0] + ): # Raise only if the `tests/upgrades` directory is selected + pytest.fail( + f'For upgrade scenarios either {PRE_UPGRADE_MARK} or {POST_UPGRADE_MARK} mark ' + 'must be provided' + ) if PRE_UPGRADE_MARK in config.option.markexpr: pre_upgrade_failed_tests = [] PRE_UPGRADE = True diff --git a/tests/upgrades/test_activation_key.py b/tests/upgrades/test_activation_key.py index bac55d78c35..11d37ccdab0 100644 --- a/tests/upgrades/test_activation_key.py +++ b/tests/upgrades/test_activation_key.py @@ -39,8 +39,7 @@ def activation_key_setup(self, request, target_sat): ak = target_sat.api.ActivationKey( content_view=cv, organization=org, name=f"{request.param}_ak" ).create() - ak_details = {'org': org, "cv": cv, 'ak': ak, 'custom_repo': custom_repo} - return ak_details + return {'org': org, "cv": cv, 'ak': ak, 'custom_repo': custom_repo} @pytest.mark.pre_upgrade @pytest.mark.parametrize( diff --git a/tests/upgrades/test_bookmarks.py b/tests/upgrades/test_bookmarks.py index b0d04cfa985..ff53bcdb30b 100644 --- a/tests/upgrades/test_bookmarks.py +++ b/tests/upgrades/test_bookmarks.py @@ -13,7 +13,7 @@ """ import pytest -from robottelo.constants import BOOKMARK_ENTITIES +from robottelo.constants import BOOKMARK_ENTITIES_SELECTION class TestPublicDisableBookmark: @@ -45,7 +45,7 @@ def test_pre_create_public_disable_bookmark(self, request, target_sat): :CaseImportance: Critical """ - for entity in BOOKMARK_ENTITIES: + for entity in BOOKMARK_ENTITIES_SELECTION: book_mark_name = entity["name"] + request.node.name bm = target_sat.api.Bookmark( controller=entity['controller'], @@ -77,7 +77,7 @@ def test_post_create_public_disable_bookmark(self, dependent_scenario_name, targ :CaseImportance: Critical """ pre_test_name = dependent_scenario_name - for entity in BOOKMARK_ENTITIES: + for entity in BOOKMARK_ENTITIES_SELECTION: book_mark_name = entity["name"] + pre_test_name bm = target_sat.api.Bookmark().search(query={'search': f'name="{book_mark_name}"'})[0] assert bm.controller == entity['controller'] @@ -115,7 +115,7 @@ def test_pre_create_public_enable_bookmark(self, request, target_sat): :customerscenario: true """ - for entity in BOOKMARK_ENTITIES: + for entity in BOOKMARK_ENTITIES_SELECTION: book_mark_name = entity["name"] + request.node.name bm = target_sat.api.Bookmark( controller=entity['controller'], @@ -145,7 +145,7 @@ def test_post_create_public_enable_bookmark(self, dependent_scenario_name, targe :CaseImportance: Critical """ pre_test_name = dependent_scenario_name - for entity in BOOKMARK_ENTITIES: + for entity in BOOKMARK_ENTITIES_SELECTION: book_mark_name = entity["name"] + pre_test_name bm = target_sat.api.Bookmark().search(query={'search': f'name="{book_mark_name}"'})[0] assert bm.controller == entity['controller'] diff --git a/tests/upgrades/test_capsule.py b/tests/upgrades/test_capsule.py index d064b677fbe..c7cf63e9977 100644 --- a/tests/upgrades/test_capsule.py +++ b/tests/upgrades/test_capsule.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: Capsule +:CaseComponent: ForemanProxy :Team: Platform diff --git a/tests/upgrades/test_classparameter.py b/tests/upgrades/test_classparameter.py index ba63148b102..b7f8d1c6ed1 100644 --- a/tests/upgrades/test_classparameter.py +++ b/tests/upgrades/test_classparameter.py @@ -82,7 +82,7 @@ def _validate_value(self, data, sc_param): :param sc_param: The Actual Value of parameter """ if data['sc_type'] == 'boolean': - assert sc_param.default_value == (True if data['value'] == '1' else False) + assert sc_param.default_value == (data['value'] == '1') elif data['sc_type'] == 'array': string_list = [str(element) for element in sc_param.default_value] assert str(string_list) == data['value'] diff --git a/tests/upgrades/test_contentview.py b/tests/upgrades/test_contentview.py index 9b7fa90bb56..2b581297b1e 100644 --- a/tests/upgrades/test_contentview.py +++ b/tests/upgrades/test_contentview.py @@ -87,6 +87,7 @@ def test_cv_postupgrade_scenario(self, request, target_sat, pre_upgrade_data): cv = target_sat.api.ContentView(organization=org.id).search( query={'search': f'name="{cv_name}"'} )[0] + request.addfinalizer(cv.delete) yum_repo = target_sat.api.Repository(organization=org.id).search( query={'search': f'name="{pre_test_name}_yum_repo"'} )[0] @@ -95,7 +96,6 @@ def test_cv_postupgrade_scenario(self, request, target_sat, pre_upgrade_data): query={'search': f'name="{pre_test_name}_file_repo"'} )[0] request.addfinalizer(file_repo.delete) - request.addfinalizer(cv.delete) cv.repository = [] cv.update(['repository']) assert len(cv.read_json()['repositories']) == 0 diff --git a/tests/upgrades/test_host.py b/tests/upgrades/test_host.py index 642fef7f2b5..b60585d63bd 100644 --- a/tests/upgrades/test_host.py +++ b/tests/upgrades/test_host.py @@ -72,7 +72,7 @@ def class_host( Later in tests this host will be used to perform assertions """ - host = sat_gce.api.Host( + return sat_gce.api.Host( architecture=sat_gce_default_architecture, compute_attributes=self.compute_attrs, domain=sat_gce_domain, @@ -85,7 +85,6 @@ def class_host( image=module_gce_finishimg, root_pass=gen_string('alphanumeric'), ).create() - return host def google_host(self, googleclient): """Returns the Google Client Host object to perform the assertions""" @@ -162,6 +161,7 @@ def test_post_create_gce_cr_and_host( pre_upgrade_host = sat_gce.api.Host().search( query={'search': f'name={pre_upgrade_data.provision_host_name}'} )[0] + request.addfinalizer(pre_upgrade_host.delete) org = sat_gce.api.Organization(id=pre_upgrade_host.organization.id).read() loc = sat_gce.api.Location(id=pre_upgrade_host.location.id).read() domain = sat_gce.api.Domain(id=pre_upgrade_host.domain.id).read() @@ -186,7 +186,6 @@ def test_post_create_gce_cr_and_host( image=image, root_pass=gen_string('alphanumeric'), ).create() - request.addfinalizer(pre_upgrade_host.delete) request.addfinalizer(host.delete) assert host.name == f"{self.hostname.lower()}.{domain.name}" assert host.build_status_label == 'Installed' diff --git a/tests/upgrades/test_performance_tuning.py b/tests/upgrades/test_performance_tuning.py index 94e40a33ba7..33237e8f561 100644 --- a/tests/upgrades/test_performance_tuning.py +++ b/tests/upgrades/test_performance_tuning.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: Installer +:CaseComponent: Installation :Team: Platform diff --git a/tests/upgrades/test_provisioningtemplate.py b/tests/upgrades/test_provisioningtemplate.py index 9d681a56ead..7c603c30a53 100644 --- a/tests/upgrades/test_provisioningtemplate.py +++ b/tests/upgrades/test_provisioningtemplate.py @@ -105,6 +105,7 @@ def test_post_scenario_provisioning_templates( pre_upgrade_host = module_target_sat.api.Host().search( query={'search': f'id={pre_upgrade_data.provision_host_id}'} )[0] + request.addfinalizer(pre_upgrade_host.delete) org = module_target_sat.api.Organization(id=pre_upgrade_host.organization.id).read() loc = module_target_sat.api.Location(id=pre_upgrade_host.location.id).read() domain = module_target_sat.api.Domain(id=pre_upgrade_host.domain.id).read() @@ -129,7 +130,6 @@ def test_post_scenario_provisioning_templates( root_pass=settings.provisioning.host_root_password, pxe_loader=pxe_loader, ).create() - request.addfinalizer(pre_upgrade_host.delete) request.addfinalizer(new_host.delete) for kind in provisioning_template_kinds: diff --git a/tests/upgrades/test_puppet.py b/tests/upgrades/test_puppet.py index 5cf7b4e9ae6..b0c96394cc8 100644 --- a/tests/upgrades/test_puppet.py +++ b/tests/upgrades/test_puppet.py @@ -18,8 +18,7 @@ def upgrade_server(request, module_target_sat, pre_configured_capsule): if request.param: return module_target_sat - else: - return pre_configured_capsule + return pre_configured_capsule class TestPuppet: diff --git a/tests/upgrades/test_satellite_maintain.py b/tests/upgrades/test_satellite_maintain.py index 5091f8508fd..03abb569e94 100644 --- a/tests/upgrades/test_satellite_maintain.py +++ b/tests/upgrades/test_satellite_maintain.py @@ -4,7 +4,7 @@ :CaseAutomation: Automated -:CaseComponent: ForemanMaintain +:CaseComponent: SatelliteMaintain :Team: Platform @@ -35,8 +35,7 @@ def satellite_upgradable_version_list(sat_obj): cmd = 'satellite-maintain upgrade list-versions --disable-self-upgrade' list_versions = sat_obj.execute(cmd).stdout regex = re.compile(r'^\d+\.\d+') - upgradeable_versions = [version for version in list_versions if regex.match(version)] - return upgradeable_versions + return [version for version in list_versions if regex.match(version)] @pytest.mark.pre_upgrade def test_pre_satellite_maintain_upgrade_list_versions(self, target_sat): diff --git a/tests/upgrades/test_subscription.py b/tests/upgrades/test_subscription.py index 70e8b53f9c1..dc6d33b84b2 100644 --- a/tests/upgrades/test_subscription.py +++ b/tests/upgrades/test_subscription.py @@ -70,7 +70,7 @@ def test_post_manifest_scenario_refresh(self, request, target_sat, pre_upgrade_d history = target_sat.api.Subscription(organization=org).manifest_history( data={'organization_id': org.id} ) - assert "Subscriptions deleted by foreman_admin" == history[0]['statusMessage'] + assert history[0]['statusMessage'] == "Subscriptions deleted by foreman_admin" class TestSubscriptionAutoAttach: @@ -173,5 +173,5 @@ def test_post_subscription_scenario_auto_attach(self, request, target_sat, pre_u sub.delete_manifest(data={'organization_id': org.id}) assert len(sub.search()) == 0 manifester = Manifester(manifest_category=settings.manifest.entitlement) - manifester.allocation_uuid = pre_upgrade_data.allocation_uuid request.addfinalizer(manifester.delete_subscription_allocation) + manifester.allocation_uuid = pre_upgrade_data.allocation_uuid diff --git a/tests/upgrades/test_usergroup.py b/tests/upgrades/test_usergroup.py index 0832602dbb3..11ac95e2af8 100644 --- a/tests/upgrades/test_usergroup.py +++ b/tests/upgrades/test_usergroup.py @@ -102,16 +102,16 @@ def test_post_verify_user_group_membership( user_group = target_sat.api.UserGroup().search( query={'search': f'name={pre_upgrade_data["user_group_name"]}'} ) + request.addfinalizer(user_group[0].delete) auth_source = target_sat.api.AuthSourceLDAP().search( query={'search': f'name={pre_upgrade_data["auth_source_name"]}'} )[0] request.addfinalizer(auth_source.delete) - request.addfinalizer(user_group[0].delete) user = target_sat.api.User().search(query={'search': f'login={ad_data["ldap_user_name"]}'})[ 0 ] - assert user.read().id == user_group[0].read().user[0].id request.addfinalizer(user.delete) + assert user.read().id == user_group[0].read().user[0].id role_list = target_sat.cli.Role.with_user( username=ad_data['ldap_user_name'], password=ad_data['ldap_user_passwd'] ).list() diff --git a/tests/upgrades/test_virtwho.py b/tests/upgrades/test_virtwho.py index 3691e3a8d70..e86868ebf37 100644 --- a/tests/upgrades/test_virtwho.py +++ b/tests/upgrades/test_virtwho.py @@ -37,7 +37,7 @@ def form_data(target_sat): 'satellite_url': target_sat.hostname, 'hypervisor_username': esx.hypervisor_username, 'hypervisor_password': esx.hypervisor_password, - 'name': 'preupgrade_virt_who', + 'name': f'preupgrade_virt_who_{gen_string("alpha")}', } @@ -57,7 +57,7 @@ class TestScenarioPositiveVirtWho: @pytest.mark.pre_upgrade def test_pre_create_virt_who_configuration( - self, form_data, save_test_data, target_sat, function_entitlement_manifest + self, form_data, save_test_data, target_sat, module_sca_manifest_org ): """Create and deploy virt-who configuration. @@ -69,57 +69,28 @@ def test_pre_create_virt_who_configuration( 1. Config can be created and deployed by command. 2. No error msg in /var/log/rhsm/rhsm.log. 3. Report is sent to satellite. - 4. Virtual sku can be generated and attached. """ - org = target_sat.api.Organization(name=ORG_DATA['name']).create() - target_sat.api.Location(organization=[org]).create() - org.sca_disable() - target_sat.upload_manifest(org.id, function_entitlement_manifest.content) - form_data.update({'organization_id': org.id}) + form_data.update({'organization_id': module_sca_manifest_org.id}) vhd = target_sat.api.VirtWhoConfig(**form_data).create() assert vhd.status == 'unknown' - command = get_configure_command(vhd.id, org=org.name) + command = get_configure_command(vhd.id, org=module_sca_manifest_org.name) hypervisor_name, guest_name = deploy_configure_by_command( - command, form_data['hypervisor_type'], debug=True, org=org.label + command, form_data['hypervisor_type'], debug=True, org=module_sca_manifest_org.label ) virt_who_instance = ( - target_sat.api.VirtWhoConfig(organization_id=org.id) + target_sat.api.VirtWhoConfig(organization_id=module_sca_manifest_org.id) .search(query={'search': f'name={form_data["name"]}'})[0] .status ) assert virt_who_instance == 'ok' - hosts = [ - (hypervisor_name, f'product_id={settings.virtwho.sku.vdc_physical} and type=NORMAL'), - (guest_name, f'product_id={settings.virtwho.sku.vdc_physical} and type=STACK_DERIVED'), - ] - for hostname, sku in hosts: - host = target_sat.cli.Host.list({'search': hostname})[0] - subscriptions = target_sat.cli.Subscription.list( - {'organization-id': org.id, 'search': sku} - ) - vdc_id = subscriptions[0]['id'] - if 'type=STACK_DERIVED' in sku: - for item in subscriptions: - if hypervisor_name.lower() in item['type']: - vdc_id = item['id'] - break - target_sat.api.HostSubscription(host=host['id']).add_subscriptions( - data={'subscriptions': [{'id': vdc_id, 'quantity': 'Automatic'}]} - ) - result = ( - target_sat.api.Host(organization=org.id) - .search(query={'search': hostname})[0] - .read_json() - ) - assert result['subscription_status_label'] == 'Fully entitled' - save_test_data( { 'hypervisor_name': hypervisor_name, 'guest_name': guest_name, - 'org_id': org.id, - 'org_name': org.name, - 'org_label': org.label, + 'org_id': module_sca_manifest_org.id, + 'org_name': module_sca_manifest_org.name, + 'org_label': module_sca_manifest_org.label, + 'name': vhd.name, } ) @@ -146,15 +117,16 @@ def test_post_crud_virt_who_configuration(self, form_data, pre_upgrade_data, tar org_id = pre_upgrade_data.get('org_id') org_name = pre_upgrade_data.get('org_name') org_label = pre_upgrade_data.get('org_label') + name = pre_upgrade_data.get('name') # Post upgrade, Verify virt-who exists and has same status. vhd = target_sat.api.VirtWhoConfig(organization_id=org_id).search( - query={'search': f'name={form_data["name"]}'} + query={'search': f'name={name}'} )[0] if not is_open('BZ:1802395'): assert vhd.status == 'ok' # Verify virt-who status via CLI as we cannot check it via API now - vhd_cli = target_sat.cli.VirtWhoConfig.exists(search=('name', form_data['name'])) + vhd_cli = target_sat.cli.VirtWhoConfig.exists(search=('name', name)) assert ( target_sat.cli.VirtWhoConfig.info({'id': vhd_cli['id']})['general-information'][ 'status' @@ -172,7 +144,7 @@ def test_post_crud_virt_who_configuration(self, form_data, pre_upgrade_data, tar .search(query={'search': hostname})[0] .read_json() ) - assert result['subscription_status_label'] == 'Fully entitled' + assert result['subscription_status_label'] == 'Simple Content Access' # Verify the virt-who config-file exists. config_file = get_configure_file(vhd.id) @@ -185,7 +157,7 @@ def test_post_crud_virt_who_configuration(self, form_data, pre_upgrade_data, tar ) virt_who_instance = ( target_sat.api.VirtWhoConfig(organization_id=org_id) - .search(query={'search': f'name={form_data["name"]}'})[0] + .search(query={'search': f'name={name}'})[0] .status ) assert virt_who_instance == 'ok'