bt-cli 0.4.37__tar.gz → 0.4.39__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bt_cli-0.4.37 → bt_cli-0.4.39}/.claude/skills/epml/SKILL.md +56 -2
- {bt_cli-0.4.37 → bt_cli-0.4.39}/CLAUDE.md +1 -1
- {bt_cli-0.4.37 → bt_cli-0.4.39}/PKG-INFO +1 -1
- {bt_cli-0.4.37 → bt_cli-0.4.39}/pyproject.toml +1 -1
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/__init__.py +1 -1
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/data/CLAUDE.md +1 -1
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/data/skills/epml/SKILL.md +56 -2
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/client/base.py +23 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/rbp_roles.py +122 -6
- {bt_cli-0.4.37/src/bt_cli/data → bt_cli-0.4.39/.claude}/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.37/src/bt_cli/data → bt_cli-0.4.39/.claude}/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.37/src/bt_cli/data → bt_cli-0.4.39/.claude}/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.37/src/bt_cli/data → bt_cli-0.4.39/.claude}/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.37/src/bt_cli/data → bt_cli-0.4.39/.claude}/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/.env.example +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/.github/workflows/ci.yml +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/.github/workflows/release.yml +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/.gitignore +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/README.md +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/assets/cli-help.png +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/assets/cli-output.png +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/bt-cli.spec +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/bt_entry.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/epml-implementation-plan.md +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/scripts/bt_entry.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/scripts/sync-package-data.sh +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/cli.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/commands/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/commands/configure.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/commands/learn.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/commands/quick.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/core/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/core/auth.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/core/client.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/core/config.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/core/config_file.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/core/csv_utils.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/core/errors.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/core/output.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/core/prompts.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/core/rest_debug.py +0 -0
- {bt_cli-0.4.37/tests/pws → bt_cli-0.4.39/src/bt_cli/data}/__init__.py +0 -0
- {bt_cli-0.4.37/.claude → bt_cli-0.4.39/src/bt_cli/data}/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.37/.claude → bt_cli-0.4.39/src/bt_cli/data}/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.37/.claude → bt_cli-0.4.39/src/bt_cli/data}/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.37/.claude → bt_cli-0.4.39/src/bt_cli/data}/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.37/.claude → bt_cli-0.4.39/src/bt_cli/data}/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/client/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/client/base.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/commands/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/commands/accounts.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/commands/applications.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/commands/auth.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/commands/bundles.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/commands/integrations.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/commands/permissions.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/commands/policies.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/commands/resources.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/commands/roles.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/commands/users.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/commands/workflows.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/models/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/models/bundle.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/models/common.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/models/integration.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/models/permission.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/models/policy.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/models/resource.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/models/role.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/models/user.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/entitle/models/workflow.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/client/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/audit.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/auth.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/client_pkg.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/external_apis.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/hosts.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/iolog.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/license.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/quick.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/rbp_cmdgrps.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/rbp_hostgrps.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/rbp_tmdategrps.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/rbp_usergrps.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/settings.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/siems.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/commands/users.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epml/models/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/client/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/client/base.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/commands/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/commands/audits.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/commands/auth.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/commands/computers.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/commands/events.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/commands/groups.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/commands/policies.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/commands/quick.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/commands/requests.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/commands/roles.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/commands/tasks.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/commands/users.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/epmw/models/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/client/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/client/base.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/commands/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/commands/auth.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/commands/import_export.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/commands/jump_clients.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/commands/jump_groups.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/commands/jump_items.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/commands/jumpoints.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/commands/policies.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/commands/quick.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/commands/teams.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/commands/users.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/commands/vault.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/models/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/models/common.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/models/jump_client.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/models/jump_group.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/models/jump_item.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/models/jumpoint.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/models/team.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/models/user.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pra/models/vault.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/client/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/client/base.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/client/beyondinsight.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/client/passwordsafe.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/accounts.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/assets.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/attributes.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/auth.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/clouds.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/config.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/credentials.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/databases.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/directories.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/functional.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/import_export.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/platforms.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/quick.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/search.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/secrets.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/systems.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/users.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/commands/workgroups.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/config.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/models/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/models/account.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/models/asset.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/models/common.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/src/bt_cli/pws/models/system.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/conftest.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/core/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/core/test_auth.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/core/test_config.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/core/test_errors.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/core/test_rest_debug.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/entitle/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/entitle/test_client.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/entitle/test_commands.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/entitle-smoke-test.sh +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/epml/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/epml/test_client.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/epml/test_commands.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/epmw/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/epmw/test_client.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/epmw/test_commands.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/epmw-quick-test-plan.md +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/fixtures/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/fixtures/responses.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/integration/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/integration/conftest.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/integration/helpers.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/integration/test_entitle_integration.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/integration/test_epmw_integration.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/integration/test_epmw_lifecycle.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/integration/test_pra_integration.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/integration/test_pra_lifecycle.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/integration/test_pws_integration.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/integration/test_pws_lifecycle.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/pra/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/pra/test_client.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/pra/test_commands.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/pra-smoke-test.sh +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/pra-test-plan.md +0 -0
- {bt_cli-0.4.37/src/bt_cli/data → bt_cli-0.4.39/tests/pws}/__init__.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/pws/test_client.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/pws/test_commands.py +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/pws-quick-test-plan.md +0 -0
- {bt_cli-0.4.37 → bt_cli-0.4.39}/tests/pws-smoke-test.sh +0 -0
|
@@ -133,8 +133,13 @@ bt epml rbp tmdategrps tmdates replace <tmdategrp_id> --file tmdates.json
|
|
|
133
133
|
# Roles + assignments
|
|
134
134
|
bt epml rbp roles list
|
|
135
135
|
bt epml rbp roles create --name "Helpdesk Role" --action A \
|
|
136
|
+
--description "..." --comment "..." --tag "AdminAccess" \
|
|
137
|
+
--risk 6 --rpt 1 \
|
|
136
138
|
--iolog '/iologs/%date%/%uniqueid%.iolog' \
|
|
137
|
-
--
|
|
139
|
+
--banner-text "Helpdesk Role" # builds standard ###-framed banner
|
|
140
|
+
# or --message "raw multi-line message"
|
|
141
|
+
# or --banner # banner with %rbprole% template
|
|
142
|
+
bt epml rbp roles update <id> --tag NewTag --risk 9 --rpt 1 # in-place edit
|
|
138
143
|
bt epml rbp roles duplicate <role_id>
|
|
139
144
|
bt epml rbp roles cmdgrps add <role_id> --ids 1,2 # cmdgrps & tmdategrps: just IDs
|
|
140
145
|
bt epml rbp roles tmdategrps add <role_id> --ids 1
|
|
@@ -158,6 +163,54 @@ bt epml rbp policy revert
|
|
|
158
163
|
bt epml rbp policy delete-all --yes-i-mean-it # destructive, gated
|
|
159
164
|
```
|
|
160
165
|
|
|
166
|
+
## Worked example: build a role from scratch
|
|
167
|
+
|
|
168
|
+
End-to-end recipe for a "users in group X can run shells on all hosts at any time" role with I/O logging and a session banner. Mirrors the pattern of the tenant's existing roles (Postgres, Docker Admin, etc.).
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# 1. Command group containing the commands the role allows
|
|
172
|
+
bt epml rbp cmdgrps create --name "RootShells" --description "Root shells with arguments"
|
|
173
|
+
# captures: id 35
|
|
174
|
+
bt epml rbp cmdgrps commands add 35 --commands "bash *,sh *,ksh *,zsh *,csh *,bash,sh,ksh,zsh,csh"
|
|
175
|
+
|
|
176
|
+
# 2. Role itself, all metadata in one shot
|
|
177
|
+
bt epml rbp roles create \
|
|
178
|
+
--name "RootShells" \
|
|
179
|
+
--description "Root shell access — I/O logged" \
|
|
180
|
+
--comment "Root shell access — I/O logged" \
|
|
181
|
+
--tag "RootAccess" \
|
|
182
|
+
--risk 9 \
|
|
183
|
+
--rpt 1 \
|
|
184
|
+
--action A \
|
|
185
|
+
--iolog '/iologs/%date%/%uniqueid%.iolog' \
|
|
186
|
+
--banner-text "Root Shell Access"
|
|
187
|
+
# captures: id 126
|
|
188
|
+
|
|
189
|
+
# 3. Wire up the four group assignments
|
|
190
|
+
bt epml rbp roles cmdgrps add 126 --ids 35 # cmdgrp: just IDs
|
|
191
|
+
bt epml rbp roles hostgrps add 126 --ids 1 --kind B # All Hosts, both Submit and Run-as
|
|
192
|
+
bt epml rbp roles usergrps add 126 --ids 4 --kind S # Administrators as Submit (who requests)
|
|
193
|
+
bt epml rbp roles usergrps add 126 --ids 3 --kind R # 'root' as Run-as (whose identity)
|
|
194
|
+
bt epml rbp roles tmdategrps add 126 --ids 1 # Any Time
|
|
195
|
+
|
|
196
|
+
# 4. Verify
|
|
197
|
+
bt epml rbp roles list -o json | python3 -c 'import json,sys;[print(json.dumps(r,indent=2)) for r in json.load(sys.stdin) if r["id"]==126]'
|
|
198
|
+
bt epml rbp roles cmdgrps list 126
|
|
199
|
+
bt epml rbp roles hostgrps list 126
|
|
200
|
+
bt epml rbp roles usergrps list 126
|
|
201
|
+
bt epml rbp roles tmdategrps list 126
|
|
202
|
+
bt epml rbp entitlement run -o json | grep RootShells # rpt=1 makes it surface
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The resulting banner displayed to users (server-substituted at session time):
|
|
206
|
+
```
|
|
207
|
+
############################################################
|
|
208
|
+
Policy: Root Shell Access
|
|
209
|
+
Status: %event%
|
|
210
|
+
Session Recorded: Yes
|
|
211
|
+
############################################################
|
|
212
|
+
```
|
|
213
|
+
|
|
161
214
|
## Test suites + transactions (the killer workflow)
|
|
162
215
|
|
|
163
216
|
```bash
|
|
@@ -206,7 +259,8 @@ bt epml quick tests-then-deploy --suite <suite_id> # commits if pass; rollb
|
|
|
206
259
|
- **Children share the parent's `id`** in GET responses — the listed `id` field is the cmdgrp/hostgrp/usergrp ID, not unique per child. The actual identifier is the `cmd`/`host`/`user` text.
|
|
207
260
|
- **Role assignments take a single object per request, not an array**. The CLI loops under the hood; the wire body is e.g. `{"cmds": 35}`, `{"hosts": 1, "type": "S"}`, `{"users": 4, "type": "R"}`, `{"tmdates": 1}`.
|
|
208
261
|
- **Hostgrp / usergrp assignments require a `type` field** (`S` = Submit / who requests, `R` = Run-as / whose identity the command runs under). Without `type` the request 400s with "RBP role type not in: [S,R]". CLI: `--kind S|R|B` on `roles hostgrps add` and `roles usergrps add`. `B` (default) creates both an S row and an R row for the same id — appropriate when the same group plays both roles. For "Admin requests, runs as root", do `--ids 4 --kind S` and `--ids 3 --kind R` separately.
|
|
209
|
-
- **Roles need `rpt: 1`** to appear in `bt epml rbp entitlement run`. The role still functions for policy evaluation either way, but only `rpt=1` roles are surfaced in the report.
|
|
262
|
+
- **Roles need `rpt: 1`** to appear in `bt epml rbp entitlement run`. The role still functions for policy evaluation either way, but only `rpt=1` roles are surfaced in the report. Set with `--rpt 1` on `roles create` / `roles update` (added in 0.4.38).
|
|
263
|
+
- **Role update is upsert-on-id, not partial**: hitting POST `/roles` with `{id, ...}` *overwrites* the matching record — fields you omit get cleared. The CLI's `roles update` does read-modify-write (fetch the role, merge changes, post the whole record) so this feels partial. Role-child relations (rolecmds/roleusers/etc.) are filtered out of the merge so assignments survive an update. **If you hit the API directly via curl**, you must send the full record yourself.
|
|
210
264
|
|
|
211
265
|
## Known gaps (TODO)
|
|
212
266
|
|
|
@@ -133,8 +133,13 @@ bt epml rbp tmdategrps tmdates replace <tmdategrp_id> --file tmdates.json
|
|
|
133
133
|
# Roles + assignments
|
|
134
134
|
bt epml rbp roles list
|
|
135
135
|
bt epml rbp roles create --name "Helpdesk Role" --action A \
|
|
136
|
+
--description "..." --comment "..." --tag "AdminAccess" \
|
|
137
|
+
--risk 6 --rpt 1 \
|
|
136
138
|
--iolog '/iologs/%date%/%uniqueid%.iolog' \
|
|
137
|
-
--
|
|
139
|
+
--banner-text "Helpdesk Role" # builds standard ###-framed banner
|
|
140
|
+
# or --message "raw multi-line message"
|
|
141
|
+
# or --banner # banner with %rbprole% template
|
|
142
|
+
bt epml rbp roles update <id> --tag NewTag --risk 9 --rpt 1 # in-place edit
|
|
138
143
|
bt epml rbp roles duplicate <role_id>
|
|
139
144
|
bt epml rbp roles cmdgrps add <role_id> --ids 1,2 # cmdgrps & tmdategrps: just IDs
|
|
140
145
|
bt epml rbp roles tmdategrps add <role_id> --ids 1
|
|
@@ -158,6 +163,54 @@ bt epml rbp policy revert
|
|
|
158
163
|
bt epml rbp policy delete-all --yes-i-mean-it # destructive, gated
|
|
159
164
|
```
|
|
160
165
|
|
|
166
|
+
## Worked example: build a role from scratch
|
|
167
|
+
|
|
168
|
+
End-to-end recipe for a "users in group X can run shells on all hosts at any time" role with I/O logging and a session banner. Mirrors the pattern of the tenant's existing roles (Postgres, Docker Admin, etc.).
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# 1. Command group containing the commands the role allows
|
|
172
|
+
bt epml rbp cmdgrps create --name "RootShells" --description "Root shells with arguments"
|
|
173
|
+
# captures: id 35
|
|
174
|
+
bt epml rbp cmdgrps commands add 35 --commands "bash *,sh *,ksh *,zsh *,csh *,bash,sh,ksh,zsh,csh"
|
|
175
|
+
|
|
176
|
+
# 2. Role itself, all metadata in one shot
|
|
177
|
+
bt epml rbp roles create \
|
|
178
|
+
--name "RootShells" \
|
|
179
|
+
--description "Root shell access — I/O logged" \
|
|
180
|
+
--comment "Root shell access — I/O logged" \
|
|
181
|
+
--tag "RootAccess" \
|
|
182
|
+
--risk 9 \
|
|
183
|
+
--rpt 1 \
|
|
184
|
+
--action A \
|
|
185
|
+
--iolog '/iologs/%date%/%uniqueid%.iolog' \
|
|
186
|
+
--banner-text "Root Shell Access"
|
|
187
|
+
# captures: id 126
|
|
188
|
+
|
|
189
|
+
# 3. Wire up the four group assignments
|
|
190
|
+
bt epml rbp roles cmdgrps add 126 --ids 35 # cmdgrp: just IDs
|
|
191
|
+
bt epml rbp roles hostgrps add 126 --ids 1 --kind B # All Hosts, both Submit and Run-as
|
|
192
|
+
bt epml rbp roles usergrps add 126 --ids 4 --kind S # Administrators as Submit (who requests)
|
|
193
|
+
bt epml rbp roles usergrps add 126 --ids 3 --kind R # 'root' as Run-as (whose identity)
|
|
194
|
+
bt epml rbp roles tmdategrps add 126 --ids 1 # Any Time
|
|
195
|
+
|
|
196
|
+
# 4. Verify
|
|
197
|
+
bt epml rbp roles list -o json | python3 -c 'import json,sys;[print(json.dumps(r,indent=2)) for r in json.load(sys.stdin) if r["id"]==126]'
|
|
198
|
+
bt epml rbp roles cmdgrps list 126
|
|
199
|
+
bt epml rbp roles hostgrps list 126
|
|
200
|
+
bt epml rbp roles usergrps list 126
|
|
201
|
+
bt epml rbp roles tmdategrps list 126
|
|
202
|
+
bt epml rbp entitlement run -o json | grep RootShells # rpt=1 makes it surface
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The resulting banner displayed to users (server-substituted at session time):
|
|
206
|
+
```
|
|
207
|
+
############################################################
|
|
208
|
+
Policy: Root Shell Access
|
|
209
|
+
Status: %event%
|
|
210
|
+
Session Recorded: Yes
|
|
211
|
+
############################################################
|
|
212
|
+
```
|
|
213
|
+
|
|
161
214
|
## Test suites + transactions (the killer workflow)
|
|
162
215
|
|
|
163
216
|
```bash
|
|
@@ -206,7 +259,8 @@ bt epml quick tests-then-deploy --suite <suite_id> # commits if pass; rollb
|
|
|
206
259
|
- **Children share the parent's `id`** in GET responses — the listed `id` field is the cmdgrp/hostgrp/usergrp ID, not unique per child. The actual identifier is the `cmd`/`host`/`user` text.
|
|
207
260
|
- **Role assignments take a single object per request, not an array**. The CLI loops under the hood; the wire body is e.g. `{"cmds": 35}`, `{"hosts": 1, "type": "S"}`, `{"users": 4, "type": "R"}`, `{"tmdates": 1}`.
|
|
208
261
|
- **Hostgrp / usergrp assignments require a `type` field** (`S` = Submit / who requests, `R` = Run-as / whose identity the command runs under). Without `type` the request 400s with "RBP role type not in: [S,R]". CLI: `--kind S|R|B` on `roles hostgrps add` and `roles usergrps add`. `B` (default) creates both an S row and an R row for the same id — appropriate when the same group plays both roles. For "Admin requests, runs as root", do `--ids 4 --kind S` and `--ids 3 --kind R` separately.
|
|
209
|
-
- **Roles need `rpt: 1`** to appear in `bt epml rbp entitlement run`. The role still functions for policy evaluation either way, but only `rpt=1` roles are surfaced in the report.
|
|
262
|
+
- **Roles need `rpt: 1`** to appear in `bt epml rbp entitlement run`. The role still functions for policy evaluation either way, but only `rpt=1` roles are surfaced in the report. Set with `--rpt 1` on `roles create` / `roles update` (added in 0.4.38).
|
|
263
|
+
- **Role update is upsert-on-id, not partial**: hitting POST `/roles` with `{id, ...}` *overwrites* the matching record — fields you omit get cleared. The CLI's `roles update` does read-modify-write (fetch the role, merge changes, post the whole record) so this feels partial. Role-child relations (rolecmds/roleusers/etc.) are filtered out of the merge so assignments survive an update. **If you hit the API directly via curl**, you must send the full record yourself.
|
|
210
264
|
|
|
211
265
|
## Known gaps (TODO)
|
|
212
266
|
|
|
@@ -476,6 +476,29 @@ class EPMLClient:
|
|
|
476
476
|
body.setdefault("action", "A")
|
|
477
477
|
return self.post(f"/api/pbul/{h}/rbp/roles", json=body)
|
|
478
478
|
|
|
479
|
+
def update_role(self, role_id: int, partial: Dict[str, Any], host_id: Optional[int] = None) -> Any:
|
|
480
|
+
"""Update an existing role (read-modify-write).
|
|
481
|
+
|
|
482
|
+
The API's create/update endpoint OVERWRITES on `id` match — fields not
|
|
483
|
+
in the body get zeroed/cleared. To make an update feel like a partial
|
|
484
|
+
modification, this helper fetches the current role, merges in your
|
|
485
|
+
changes, and posts the merged record. Avoids accidentally clobbering
|
|
486
|
+
`action`, `name`, `iolog`, etc. when you only meant to change `tag`.
|
|
487
|
+
|
|
488
|
+
Caveat: the v1 API has no GET-single-role, so we list and filter.
|
|
489
|
+
"""
|
|
490
|
+
h = self.host(host_id)
|
|
491
|
+
roles = self.list_roles(host_id=host_id) or []
|
|
492
|
+
current = next((r for r in roles if r.get("id") == role_id), None)
|
|
493
|
+
if current is None:
|
|
494
|
+
raise ValueError(f"role id {role_id} not found")
|
|
495
|
+
# role child relations (rolecmds/roleusers/etc.) get filtered out so we
|
|
496
|
+
# don't accidentally rewrite assignments — those have their own endpoints.
|
|
497
|
+
merged = {k: v for k, v in current.items() if not k.startswith("role")}
|
|
498
|
+
merged.update(partial)
|
|
499
|
+
merged["id"] = role_id
|
|
500
|
+
return self.post(f"/api/pbul/{h}/rbp/roles", json=merged)
|
|
501
|
+
|
|
479
502
|
def delete_roles(self, ids: List[int], host_id: Optional[int] = None) -> None:
|
|
480
503
|
h = self.host(host_id)
|
|
481
504
|
for rid in ids:
|
|
@@ -14,6 +14,37 @@ def _host_opt():
|
|
|
14
14
|
return typer.Option(None, "--host", "-H", help="PMUL host id (default: BT_EPML_DEFAULT_HOST)")
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
_BAR = "#" * 60
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _build_banner(title: Optional[str] = None) -> str:
|
|
21
|
+
"""Build a banner-style policy message.
|
|
22
|
+
|
|
23
|
+
Server substitutes `%rbprole%` and `%event%` at session time. Pass a
|
|
24
|
+
title string to replace the role-name line with literal text.
|
|
25
|
+
"""
|
|
26
|
+
title_line = f" Policy: {title}\r\n" if title else " Policy: %rbprole%\r\n"
|
|
27
|
+
return (
|
|
28
|
+
"\r\n"
|
|
29
|
+
+ _BAR + "\r\n"
|
|
30
|
+
+ title_line
|
|
31
|
+
+ " Status: %event%\r\n"
|
|
32
|
+
+ " Session Recorded: Yes\r\n"
|
|
33
|
+
+ _BAR + "\r\n"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _resolve_message(message: Optional[str], banner: bool, banner_text: Optional[str]) -> Optional[str]:
|
|
38
|
+
"""Pick the final message: explicit --message wins; else build a banner if asked."""
|
|
39
|
+
if message is not None:
|
|
40
|
+
return message
|
|
41
|
+
if banner_text is not None:
|
|
42
|
+
return _build_banner(banner_text)
|
|
43
|
+
if banner:
|
|
44
|
+
return _build_banner()
|
|
45
|
+
return None # leave message unset
|
|
46
|
+
|
|
47
|
+
|
|
17
48
|
@app.command("list")
|
|
18
49
|
def list_roles(
|
|
19
50
|
host: Optional[int] = _host_opt(),
|
|
@@ -45,13 +76,18 @@ def list_roles(
|
|
|
45
76
|
def create_role(
|
|
46
77
|
name: str = typer.Option(..., "--name", "-n"),
|
|
47
78
|
description: str = typer.Option("", "--description", "-d"),
|
|
79
|
+
comment: Optional[str] = typer.Option(None, "--comment", "-c", help="Internal comment (defaults to description if omitted)"),
|
|
80
|
+
tag: Optional[str] = typer.Option(None, "--tag", help="Free-form tag for filtering/reporting"),
|
|
81
|
+
risk: Optional[int] = typer.Option(None, "--risk", "-r", help="Risk score 0-9 (Postgres-style example uses 6)"),
|
|
82
|
+
rpt: Optional[int] = typer.Option(None, "--rpt", help="Entitlement reporting: 1 to surface in `entitlement run`, 0 to hide"),
|
|
48
83
|
action: str = typer.Option("A", "--action", "-a", help="Role verdict: A=Allow, R=Reject"),
|
|
49
84
|
iolog: Optional[str] = typer.Option(
|
|
50
85
|
None, "--iolog",
|
|
51
86
|
help="I/O log path template (e.g. /iologs/%date%/%uniqueid%.iolog). Omit to disable.",
|
|
52
87
|
),
|
|
53
|
-
message: Optional[str] = typer.Option(None, "--message", "-m", help="
|
|
54
|
-
|
|
88
|
+
message: Optional[str] = typer.Option(None, "--message", "-m", help="Raw message shown to the requesting user (multi-line OK; verbatim)"),
|
|
89
|
+
banner: bool = typer.Option(False, "--banner", help="Use a standard ###-framed banner with %rbprole% and %event% template variables"),
|
|
90
|
+
banner_text: Optional[str] = typer.Option(None, "--banner-text", help="Like --banner but use the given title text instead of %rbprole% substitution"),
|
|
55
91
|
disabled: bool = typer.Option(False, "--disabled", help="Create the role disabled"),
|
|
56
92
|
host: Optional[int] = _host_opt(),
|
|
57
93
|
):
|
|
@@ -62,18 +98,42 @@ def create_role(
|
|
|
62
98
|
|
|
63
99
|
`iolog` accepts the appliance's path template syntax — typical value is
|
|
64
100
|
`/iologs/%date%/%uniqueid%.iolog`. Omit to disable I/O logging on this role.
|
|
101
|
+
|
|
102
|
+
Three ways to set the user-visible message (in priority order):
|
|
103
|
+
--message "raw text" verbatim
|
|
104
|
+
--banner-text "Custom Title" builds the standard banner with this title
|
|
105
|
+
--banner builds the standard banner using %rbprole%
|
|
106
|
+
|
|
107
|
+
The standard banner format mirrors the appliance's existing roles:
|
|
108
|
+
##############################
|
|
109
|
+
Policy: <title or %rbprole%>
|
|
110
|
+
Status: %event%
|
|
111
|
+
Session Recorded: Yes
|
|
112
|
+
##############################
|
|
65
113
|
"""
|
|
66
114
|
from bt_cli.epml.client import get_client
|
|
67
115
|
if action not in ("A", "R"):
|
|
68
116
|
typer.echo(f"--action must be 'A' or 'R', got {action!r}", err=True)
|
|
69
117
|
raise typer.Exit(2)
|
|
118
|
+
|
|
70
119
|
body = {"name": name, "description": description, "action": action, "disabled": disabled}
|
|
71
|
-
if iolog is not None:
|
|
72
|
-
body["iolog"] = iolog
|
|
73
|
-
if message is not None:
|
|
74
|
-
body["message"] = message
|
|
75
120
|
if comment is not None:
|
|
76
121
|
body["comment"] = comment
|
|
122
|
+
elif description:
|
|
123
|
+
body["comment"] = description # mirror description into comment (matches existing roles)
|
|
124
|
+
if tag is not None:
|
|
125
|
+
body["tag"] = tag
|
|
126
|
+
if risk is not None:
|
|
127
|
+
body["risk"] = risk
|
|
128
|
+
if rpt is not None:
|
|
129
|
+
body["rpt"] = rpt
|
|
130
|
+
if iolog is not None:
|
|
131
|
+
body["iolog"] = iolog
|
|
132
|
+
|
|
133
|
+
final_message = _resolve_message(message, banner, banner_text)
|
|
134
|
+
if final_message is not None:
|
|
135
|
+
body["message"] = final_message
|
|
136
|
+
|
|
77
137
|
try:
|
|
78
138
|
with get_client() as c:
|
|
79
139
|
result = c.create_role(body, host_id=host)
|
|
@@ -84,6 +144,62 @@ def create_role(
|
|
|
84
144
|
print_api_error(e, "create role"); raise typer.Exit(1)
|
|
85
145
|
|
|
86
146
|
|
|
147
|
+
@app.command("update")
|
|
148
|
+
def update_role(
|
|
149
|
+
role_id: int = typer.Argument(..., help="Role ID to update"),
|
|
150
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="Rename the role"),
|
|
151
|
+
description: Optional[str] = typer.Option(None, "--description", "-d"),
|
|
152
|
+
comment: Optional[str] = typer.Option(None, "--comment", "-c"),
|
|
153
|
+
tag: Optional[str] = typer.Option(None, "--tag"),
|
|
154
|
+
risk: Optional[int] = typer.Option(None, "--risk", "-r"),
|
|
155
|
+
rpt: Optional[int] = typer.Option(None, "--rpt"),
|
|
156
|
+
action: Optional[str] = typer.Option(None, "--action", "-a", help="A=Allow / R=Reject"),
|
|
157
|
+
iolog: Optional[str] = typer.Option(None, "--iolog", help="I/O log template; pass '' to disable"),
|
|
158
|
+
message: Optional[str] = typer.Option(None, "--message", "-m"),
|
|
159
|
+
banner: bool = typer.Option(False, "--banner"),
|
|
160
|
+
banner_text: Optional[str] = typer.Option(None, "--banner-text"),
|
|
161
|
+
disabled: Optional[bool] = typer.Option(None, "--disabled/--enabled"),
|
|
162
|
+
host: Optional[int] = _host_opt(),
|
|
163
|
+
):
|
|
164
|
+
"""Update a role's metadata in place. Only fields you pass are modified.
|
|
165
|
+
|
|
166
|
+
Examples:
|
|
167
|
+
bt epml rbp roles update 126 --tag "RootAccess" --risk 9 --rpt 1
|
|
168
|
+
bt epml rbp roles update 126 --banner-text "Root Shell Access"
|
|
169
|
+
bt epml rbp roles update 126 --message "Custom session header..."
|
|
170
|
+
"""
|
|
171
|
+
from bt_cli.epml.client import get_client
|
|
172
|
+
if action is not None and action not in ("A", "R"):
|
|
173
|
+
typer.echo(f"--action must be 'A' or 'R', got {action!r}", err=True)
|
|
174
|
+
raise typer.Exit(2)
|
|
175
|
+
|
|
176
|
+
body: dict = {}
|
|
177
|
+
for key, val in (
|
|
178
|
+
("name", name), ("description", description), ("comment", comment),
|
|
179
|
+
("tag", tag), ("risk", risk), ("rpt", rpt), ("action", action),
|
|
180
|
+
("iolog", iolog), ("disabled", disabled),
|
|
181
|
+
):
|
|
182
|
+
if val is not None:
|
|
183
|
+
body[key] = val
|
|
184
|
+
|
|
185
|
+
final_message = _resolve_message(message, banner, banner_text)
|
|
186
|
+
if final_message is not None:
|
|
187
|
+
body["message"] = final_message
|
|
188
|
+
|
|
189
|
+
if not body:
|
|
190
|
+
typer.echo("Nothing to update — pass at least one field flag.", err=True)
|
|
191
|
+
raise typer.Exit(2)
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
with get_client() as c:
|
|
195
|
+
result = c.update_role(role_id, body, host_id=host)
|
|
196
|
+
print_json(result)
|
|
197
|
+
except httpx.HTTPStatusError as e:
|
|
198
|
+
print_api_error(e, "update role"); raise typer.Exit(1)
|
|
199
|
+
except Exception as e:
|
|
200
|
+
print_api_error(e, "update role"); raise typer.Exit(1)
|
|
201
|
+
|
|
202
|
+
|
|
87
203
|
@app.command("delete")
|
|
88
204
|
def delete_role(
|
|
89
205
|
ids: str = typer.Argument(..., help="Comma-separated role IDs"),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|