bt-cli 0.4.34__tar.gz → 0.4.36__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.34/src/bt_cli/data → bt_cli-0.4.36/.claude}/skills/epml/SKILL.md +52 -6
- {bt_cli-0.4.34 → bt_cli-0.4.36}/CLAUDE.md +1 -1
- {bt_cli-0.4.34 → bt_cli-0.4.36}/PKG-INFO +1 -1
- {bt_cli-0.4.34 → bt_cli-0.4.36}/pyproject.toml +1 -1
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/__init__.py +1 -1
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/cli.py +15 -7
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/data/CLAUDE.md +1 -1
- {bt_cli-0.4.34/.claude → bt_cli-0.4.36/src/bt_cli/data}/skills/epml/SKILL.md +52 -6
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/client/base.py +79 -9
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/rbp_cmdgrps.py +34 -10
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/rbp_hostgrps.py +34 -10
- bt_cli-0.4.36/src/bt_cli/epml/commands/rbp_tmdategrps.py +251 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/rbp_usergrps.py +67 -10
- bt_cli-0.4.34/src/bt_cli/epml/commands/rbp_tmdategrps.py +0 -77
- {bt_cli-0.4.34/src/bt_cli/data → bt_cli-0.4.36/.claude}/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.34/src/bt_cli/data → bt_cli-0.4.36/.claude}/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.34/src/bt_cli/data → bt_cli-0.4.36/.claude}/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.34/src/bt_cli/data → bt_cli-0.4.36/.claude}/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.34/src/bt_cli/data → bt_cli-0.4.36/.claude}/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/.env.example +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/.github/workflows/ci.yml +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/.github/workflows/release.yml +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/.gitignore +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/README.md +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/assets/cli-help.png +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/assets/cli-output.png +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/bt-cli.spec +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/bt_entry.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/epml-implementation-plan.md +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/scripts/bt_entry.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/scripts/sync-package-data.sh +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/commands/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/commands/configure.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/commands/learn.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/commands/quick.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/core/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/core/auth.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/core/client.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/core/config.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/core/config_file.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/core/csv_utils.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/core/errors.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/core/output.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/core/prompts.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/core/rest_debug.py +0 -0
- {bt_cli-0.4.34/tests/pws → bt_cli-0.4.36/src/bt_cli/data}/__init__.py +0 -0
- {bt_cli-0.4.34/.claude → bt_cli-0.4.36/src/bt_cli/data}/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.34/.claude → bt_cli-0.4.36/src/bt_cli/data}/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.34/.claude → bt_cli-0.4.36/src/bt_cli/data}/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.34/.claude → bt_cli-0.4.36/src/bt_cli/data}/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.34/.claude → bt_cli-0.4.36/src/bt_cli/data}/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/client/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/client/base.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/commands/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/commands/accounts.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/commands/applications.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/commands/auth.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/commands/bundles.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/commands/integrations.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/commands/permissions.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/commands/policies.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/commands/resources.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/commands/roles.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/commands/users.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/commands/workflows.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/models/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/models/bundle.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/models/common.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/models/integration.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/models/permission.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/models/policy.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/models/resource.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/models/role.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/models/user.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/entitle/models/workflow.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/client/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/audit.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/auth.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/client_pkg.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/external_apis.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/hosts.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/iolog.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/license.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/quick.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/rbp_roles.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/settings.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/siems.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/commands/users.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epml/models/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/client/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/client/base.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/commands/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/commands/audits.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/commands/auth.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/commands/computers.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/commands/events.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/commands/groups.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/commands/policies.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/commands/quick.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/commands/requests.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/commands/roles.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/commands/tasks.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/commands/users.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/epmw/models/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/client/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/client/base.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/commands/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/commands/auth.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/commands/import_export.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/commands/jump_clients.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/commands/jump_groups.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/commands/jump_items.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/commands/jumpoints.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/commands/policies.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/commands/quick.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/commands/teams.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/commands/users.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/commands/vault.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/models/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/models/common.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/models/jump_client.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/models/jump_group.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/models/jump_item.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/models/jumpoint.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/models/team.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/models/user.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pra/models/vault.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/client/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/client/base.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/client/beyondinsight.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/client/passwordsafe.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/accounts.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/assets.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/attributes.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/auth.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/clouds.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/config.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/credentials.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/databases.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/directories.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/functional.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/import_export.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/platforms.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/quick.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/search.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/secrets.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/systems.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/users.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/commands/workgroups.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/config.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/models/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/models/account.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/models/asset.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/models/common.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/src/bt_cli/pws/models/system.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/conftest.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/core/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/core/test_auth.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/core/test_config.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/core/test_errors.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/core/test_rest_debug.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/entitle/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/entitle/test_client.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/entitle/test_commands.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/entitle-smoke-test.sh +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/epml/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/epml/test_client.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/epml/test_commands.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/epmw/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/epmw/test_client.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/epmw/test_commands.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/epmw-quick-test-plan.md +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/fixtures/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/fixtures/responses.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/integration/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/integration/conftest.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/integration/helpers.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/integration/test_entitle_integration.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/integration/test_epmw_integration.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/integration/test_epmw_lifecycle.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/integration/test_pra_integration.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/integration/test_pra_lifecycle.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/integration/test_pws_integration.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/integration/test_pws_lifecycle.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/pra/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/pra/test_client.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/pra/test_commands.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/pra-smoke-test.sh +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/pra-test-plan.md +0 -0
- {bt_cli-0.4.34/src/bt_cli/data → bt_cli-0.4.36/tests/pws}/__init__.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/pws/test_client.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/pws/test_commands.py +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/pws-quick-test-plan.md +0 -0
- {bt_cli-0.4.34 → bt_cli-0.4.36}/tests/pws-smoke-test.sh +0 -0
|
@@ -103,21 +103,32 @@ bt epml rbp cmdgrps create --name "Admin Tools" --description "..."
|
|
|
103
103
|
bt epml rbp cmdgrps delete 12,13
|
|
104
104
|
bt epml rbp cmdgrps commands list <cmdgrp_id>
|
|
105
105
|
bt epml rbp cmdgrps commands add <cmdgrp_id> --commands "vi,less,cat"
|
|
106
|
-
bt epml rbp cmdgrps commands
|
|
106
|
+
bt epml rbp cmdgrps commands clear <cmdgrp_id> # NUKE all (no per-item delete)
|
|
107
|
+
bt epml rbp cmdgrps commands replace <cmdgrp_id> --commands "vi,more"
|
|
107
108
|
|
|
108
|
-
# Host groups
|
|
109
|
+
# Host groups (`type` defaults to I=Internal)
|
|
109
110
|
bt epml rbp hostgrps list
|
|
110
|
-
bt epml rbp hostgrps create --name "Admin Hosts"
|
|
111
|
+
bt epml rbp hostgrps create --name "Admin Hosts" --type I
|
|
111
112
|
bt epml rbp hostgrps hosts add <hostgrp_id> --hosts "web-*,db-*"
|
|
113
|
+
bt epml rbp hostgrps hosts clear <hostgrp_id>
|
|
114
|
+
bt epml rbp hostgrps hosts replace <hostgrp_id> --hosts "web-01,web-02"
|
|
112
115
|
|
|
113
|
-
# User groups
|
|
116
|
+
# User groups (`type` defaults to I=Internal)
|
|
114
117
|
bt epml rbp usergrps list
|
|
115
|
-
bt epml rbp usergrps create --name "Helpdesk"
|
|
118
|
+
bt epml rbp usergrps create --name "Helpdesk" --type I
|
|
119
|
+
bt epml rbp usergrps create-multiple groups.json # bulk create from JSON array
|
|
116
120
|
bt epml rbp usergrps users add <usergrp_id> --users "alice,bob"
|
|
121
|
+
bt epml rbp usergrps users clear <usergrp_id>
|
|
122
|
+
bt epml rbp usergrps users replace <usergrp_id> --users "alice"
|
|
117
123
|
|
|
118
|
-
# Time/date groups
|
|
124
|
+
# Time/date groups + tmdates
|
|
119
125
|
bt epml rbp tmdategrps list
|
|
120
126
|
bt epml rbp tmdategrps create --name "Working Hours"
|
|
127
|
+
bt epml rbp tmdategrps tmdates list <tmdategrp_id>
|
|
128
|
+
bt epml rbp tmdategrps tmdates add <tmdategrp_id> --from 09:00 --to 17:00 -d "weekday"
|
|
129
|
+
bt epml rbp tmdategrps tmdates add <tmdategrp_id> --file tmdates.json
|
|
130
|
+
bt epml rbp tmdategrps tmdates clear <tmdategrp_id>
|
|
131
|
+
bt epml rbp tmdategrps tmdates replace <tmdategrp_id> --file tmdates.json
|
|
121
132
|
|
|
122
133
|
# Roles + assignments
|
|
123
134
|
bt epml rbp roles list
|
|
@@ -179,6 +190,41 @@ bt epml quick tests-then-deploy --suite <suite_id> # commits if pass; rollb
|
|
|
179
190
|
- `bt epml rbp tx commit` — applies in-flight changes
|
|
180
191
|
- `bt epml client-pkg ...` POST endpoints — triggers a real installer build
|
|
181
192
|
|
|
193
|
+
## API quirks worth knowing (caught while building, not in the spec)
|
|
194
|
+
|
|
195
|
+
- **Create-role `action`**: required despite spec saying optional. Must be exactly `"A"` (Allow) or `"R"` (Reject). CLI defaults to `A`.
|
|
196
|
+
- **Create-hostgrp / create-usergrp `type`**: required. Must be `"I"` (Internal — static list) or `"E"` (External — directory-resolved). CLI defaults to `I`.
|
|
197
|
+
- **Collection-level deletes** (cmdgrps/hostgrps/usergrps/tmdategrps/roles): the spec shows `id` as a query parameter, NOT a JSON body. Repeated `?id=A&id=B` is accepted at the HTTP layer but only the first value is honored — the CLI loops single-id requests under the hood.
|
|
198
|
+
- **Child-collection deletes** (commands inside cmdgrp, hosts inside hostgrp, users inside usergrp, tmdates inside tmdategrp): there is **no per-item delete**. `DELETE /<parent>/{id}/<children>` clears the whole list. To remove one entry, use `replace` (CLI does GET + DELETE-all + POST under the hood).
|
|
199
|
+
- **Add-children body shape**: each child item must be wrapped: commands → `[{"cmd": x}]`, hosts → `[{"host": x}]`, users → `[{"user": x}]`, tmdates → `{"tmdates": [...]}` (note the wrapper for tmdates; the spec doesn't document any of these wrappers).
|
|
200
|
+
- **`POST /usergrps/multiple`** (bulk create): body is `{"usergroups": [...]}` — undocumented wrapper key.
|
|
201
|
+
- **POST on child collections is additive**, not replacing. Calling `commands add` twice with the same command will get you a duplicate.
|
|
202
|
+
- **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.
|
|
203
|
+
|
|
204
|
+
## Known gaps (TODO)
|
|
205
|
+
|
|
206
|
+
- **`bt epml rbp roles get <id>`** — there is no GET-single-role in the v1 API. The detailed view (role with all child relations resolved) only exists at `/api/v6/pbul/{hostid}/rbp/roledetail/{id}`, which is on the IAM-only authorizer. Not callable with a PAT today. Workaround: use `bt epml rbp roles list` for the role row, then `bt epml rbp roles {cmdgrps,hostgrps,usergrps,tmdategrps} list <id>` to fetch each child relation.
|
|
207
|
+
|
|
208
|
+
## Tenant-side gotchas (not a CLI bug)
|
|
209
|
+
|
|
210
|
+
- **`bt epml rbp tx begin` may return** `400 "RBP Transactions not enabled on host"`. This is a per-tenant feature flag on the PMUL host — transactions need to be enabled at the appliance / settings level before they'll work. The CLI surfaces the server's exact message; nothing client-side to fix. (Verified 2026-05-01 against the lab tenant — host id 100 does not have transactions enabled.) The `quick tests-then-deploy` workflow depends on transactions, so it'll also fail until the host is configured for it.
|
|
211
|
+
|
|
212
|
+
## tmdate shape (`dotw` map)
|
|
213
|
+
|
|
214
|
+
The OpenAPI spec describes `tmdate` as a `string` field with `x-go-name: "JsonDefinition"`. The actual runtime shape is a structured object:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{"dotw": {
|
|
218
|
+
"mon": [{"from":"09:00","to":"17:00"}],
|
|
219
|
+
"tue": [{"from":"09:00","to":"17:00"}],
|
|
220
|
+
...
|
|
221
|
+
"sat": [],
|
|
222
|
+
"sun": []
|
|
223
|
+
}}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
A day with an empty list is inactive. A day can have multiple windows (e.g. split shift). The CLI's `--from/--to/--days` short flags build the map for you; for anything more complex use `--file` with the full JSON.
|
|
227
|
+
|
|
182
228
|
## Path-version policy (CLI internal)
|
|
183
229
|
|
|
184
230
|
Where the spec offers both legacy `/api/pbul/{hostid}/rbp/<x>` and newer
|
|
@@ -382,10 +382,10 @@ def tree_command(
|
|
|
382
382
|
epml.add("[green]external-apis[/green] list|get")
|
|
383
383
|
epml.add("[green]iolog[/green] list")
|
|
384
384
|
rbp = epml.add("[green]rbp[/green] - Role-Based Policy")
|
|
385
|
-
rbp.add("cmdgrps list|create|delete + commands list|add|
|
|
386
|
-
rbp.add("hostgrps list|create|delete + hosts list|add|
|
|
387
|
-
rbp.add("usergrps list|create|delete + users list|add|
|
|
388
|
-
rbp.add("tmdategrps list|create|delete")
|
|
385
|
+
rbp.add("cmdgrps list|create|delete + commands list|add|clear|replace")
|
|
386
|
+
rbp.add("hostgrps list|create|delete + hosts list|add|clear|replace")
|
|
387
|
+
rbp.add("usergrps list|create|create-multiple|delete + users list|add|clear|replace")
|
|
388
|
+
rbp.add("tmdategrps list|create|delete + tmdates list|add|clear|replace")
|
|
389
389
|
rbp.add("roles list|create|delete|duplicate")
|
|
390
390
|
rbp.add(" + cmdgrps|hostgrps|usergrps|tmdategrps list|add|remove")
|
|
391
391
|
rbp.add("entitlement run [--raw]")
|
|
@@ -515,22 +515,30 @@ def _get_all_commands() -> list[tuple[str, str]]:
|
|
|
515
515
|
("bt epml rbp cmdgrps delete", "Delete RBP command groups"),
|
|
516
516
|
("bt epml rbp cmdgrps commands list", "List commands within a cmdgrp"),
|
|
517
517
|
("bt epml rbp cmdgrps commands add", "Add commands to a cmdgrp"),
|
|
518
|
-
("bt epml rbp cmdgrps commands
|
|
518
|
+
("bt epml rbp cmdgrps commands clear", "Clear ALL commands in a cmdgrp"),
|
|
519
|
+
("bt epml rbp cmdgrps commands replace", "Replace cmdgrp's full command list"),
|
|
519
520
|
("bt epml rbp hostgrps list", "List RBP host groups"),
|
|
520
521
|
("bt epml rbp hostgrps create", "Create an RBP host group"),
|
|
521
522
|
("bt epml rbp hostgrps delete", "Delete RBP host groups"),
|
|
522
523
|
("bt epml rbp hostgrps hosts list", "List hosts within a hostgrp"),
|
|
523
524
|
("bt epml rbp hostgrps hosts add", "Add hosts to a hostgrp"),
|
|
524
|
-
("bt epml rbp hostgrps hosts
|
|
525
|
+
("bt epml rbp hostgrps hosts clear", "Clear ALL hosts in a hostgrp"),
|
|
526
|
+
("bt epml rbp hostgrps hosts replace", "Replace hostgrp's full host list"),
|
|
525
527
|
("bt epml rbp usergrps list", "List RBP user groups"),
|
|
526
528
|
("bt epml rbp usergrps create", "Create an RBP user group"),
|
|
529
|
+
("bt epml rbp usergrps create-multiple", "Bulk-create user groups from a JSON file"),
|
|
527
530
|
("bt epml rbp usergrps delete", "Delete RBP user groups"),
|
|
528
531
|
("bt epml rbp usergrps users list", "List users within a usergrp"),
|
|
529
532
|
("bt epml rbp usergrps users add", "Add users to a usergrp"),
|
|
530
|
-
("bt epml rbp usergrps users
|
|
533
|
+
("bt epml rbp usergrps users clear", "Clear ALL users in a usergrp"),
|
|
534
|
+
("bt epml rbp usergrps users replace", "Replace usergrp's full user list"),
|
|
531
535
|
("bt epml rbp tmdategrps list", "List RBP time/date groups"),
|
|
532
536
|
("bt epml rbp tmdategrps create", "Create an RBP time/date group"),
|
|
533
537
|
("bt epml rbp tmdategrps delete", "Delete RBP time/date groups"),
|
|
538
|
+
("bt epml rbp tmdategrps tmdates list", "List time/date entries in a group"),
|
|
539
|
+
("bt epml rbp tmdategrps tmdates add", "Add time/date entries"),
|
|
540
|
+
("bt epml rbp tmdategrps tmdates clear", "Clear ALL time/dates in a group"),
|
|
541
|
+
("bt epml rbp tmdategrps tmdates replace", "Replace the group's full tmdate list"),
|
|
534
542
|
("bt epml rbp roles list", "List RBP roles"),
|
|
535
543
|
("bt epml rbp roles create", "Create an RBP role"),
|
|
536
544
|
("bt epml rbp roles delete", "Delete RBP roles"),
|
|
@@ -103,21 +103,32 @@ bt epml rbp cmdgrps create --name "Admin Tools" --description "..."
|
|
|
103
103
|
bt epml rbp cmdgrps delete 12,13
|
|
104
104
|
bt epml rbp cmdgrps commands list <cmdgrp_id>
|
|
105
105
|
bt epml rbp cmdgrps commands add <cmdgrp_id> --commands "vi,less,cat"
|
|
106
|
-
bt epml rbp cmdgrps commands
|
|
106
|
+
bt epml rbp cmdgrps commands clear <cmdgrp_id> # NUKE all (no per-item delete)
|
|
107
|
+
bt epml rbp cmdgrps commands replace <cmdgrp_id> --commands "vi,more"
|
|
107
108
|
|
|
108
|
-
# Host groups
|
|
109
|
+
# Host groups (`type` defaults to I=Internal)
|
|
109
110
|
bt epml rbp hostgrps list
|
|
110
|
-
bt epml rbp hostgrps create --name "Admin Hosts"
|
|
111
|
+
bt epml rbp hostgrps create --name "Admin Hosts" --type I
|
|
111
112
|
bt epml rbp hostgrps hosts add <hostgrp_id> --hosts "web-*,db-*"
|
|
113
|
+
bt epml rbp hostgrps hosts clear <hostgrp_id>
|
|
114
|
+
bt epml rbp hostgrps hosts replace <hostgrp_id> --hosts "web-01,web-02"
|
|
112
115
|
|
|
113
|
-
# User groups
|
|
116
|
+
# User groups (`type` defaults to I=Internal)
|
|
114
117
|
bt epml rbp usergrps list
|
|
115
|
-
bt epml rbp usergrps create --name "Helpdesk"
|
|
118
|
+
bt epml rbp usergrps create --name "Helpdesk" --type I
|
|
119
|
+
bt epml rbp usergrps create-multiple groups.json # bulk create from JSON array
|
|
116
120
|
bt epml rbp usergrps users add <usergrp_id> --users "alice,bob"
|
|
121
|
+
bt epml rbp usergrps users clear <usergrp_id>
|
|
122
|
+
bt epml rbp usergrps users replace <usergrp_id> --users "alice"
|
|
117
123
|
|
|
118
|
-
# Time/date groups
|
|
124
|
+
# Time/date groups + tmdates
|
|
119
125
|
bt epml rbp tmdategrps list
|
|
120
126
|
bt epml rbp tmdategrps create --name "Working Hours"
|
|
127
|
+
bt epml rbp tmdategrps tmdates list <tmdategrp_id>
|
|
128
|
+
bt epml rbp tmdategrps tmdates add <tmdategrp_id> --from 09:00 --to 17:00 -d "weekday"
|
|
129
|
+
bt epml rbp tmdategrps tmdates add <tmdategrp_id> --file tmdates.json
|
|
130
|
+
bt epml rbp tmdategrps tmdates clear <tmdategrp_id>
|
|
131
|
+
bt epml rbp tmdategrps tmdates replace <tmdategrp_id> --file tmdates.json
|
|
121
132
|
|
|
122
133
|
# Roles + assignments
|
|
123
134
|
bt epml rbp roles list
|
|
@@ -179,6 +190,41 @@ bt epml quick tests-then-deploy --suite <suite_id> # commits if pass; rollb
|
|
|
179
190
|
- `bt epml rbp tx commit` — applies in-flight changes
|
|
180
191
|
- `bt epml client-pkg ...` POST endpoints — triggers a real installer build
|
|
181
192
|
|
|
193
|
+
## API quirks worth knowing (caught while building, not in the spec)
|
|
194
|
+
|
|
195
|
+
- **Create-role `action`**: required despite spec saying optional. Must be exactly `"A"` (Allow) or `"R"` (Reject). CLI defaults to `A`.
|
|
196
|
+
- **Create-hostgrp / create-usergrp `type`**: required. Must be `"I"` (Internal — static list) or `"E"` (External — directory-resolved). CLI defaults to `I`.
|
|
197
|
+
- **Collection-level deletes** (cmdgrps/hostgrps/usergrps/tmdategrps/roles): the spec shows `id` as a query parameter, NOT a JSON body. Repeated `?id=A&id=B` is accepted at the HTTP layer but only the first value is honored — the CLI loops single-id requests under the hood.
|
|
198
|
+
- **Child-collection deletes** (commands inside cmdgrp, hosts inside hostgrp, users inside usergrp, tmdates inside tmdategrp): there is **no per-item delete**. `DELETE /<parent>/{id}/<children>` clears the whole list. To remove one entry, use `replace` (CLI does GET + DELETE-all + POST under the hood).
|
|
199
|
+
- **Add-children body shape**: each child item must be wrapped: commands → `[{"cmd": x}]`, hosts → `[{"host": x}]`, users → `[{"user": x}]`, tmdates → `{"tmdates": [...]}` (note the wrapper for tmdates; the spec doesn't document any of these wrappers).
|
|
200
|
+
- **`POST /usergrps/multiple`** (bulk create): body is `{"usergroups": [...]}` — undocumented wrapper key.
|
|
201
|
+
- **POST on child collections is additive**, not replacing. Calling `commands add` twice with the same command will get you a duplicate.
|
|
202
|
+
- **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.
|
|
203
|
+
|
|
204
|
+
## Known gaps (TODO)
|
|
205
|
+
|
|
206
|
+
- **`bt epml rbp roles get <id>`** — there is no GET-single-role in the v1 API. The detailed view (role with all child relations resolved) only exists at `/api/v6/pbul/{hostid}/rbp/roledetail/{id}`, which is on the IAM-only authorizer. Not callable with a PAT today. Workaround: use `bt epml rbp roles list` for the role row, then `bt epml rbp roles {cmdgrps,hostgrps,usergrps,tmdategrps} list <id>` to fetch each child relation.
|
|
207
|
+
|
|
208
|
+
## Tenant-side gotchas (not a CLI bug)
|
|
209
|
+
|
|
210
|
+
- **`bt epml rbp tx begin` may return** `400 "RBP Transactions not enabled on host"`. This is a per-tenant feature flag on the PMUL host — transactions need to be enabled at the appliance / settings level before they'll work. The CLI surfaces the server's exact message; nothing client-side to fix. (Verified 2026-05-01 against the lab tenant — host id 100 does not have transactions enabled.) The `quick tests-then-deploy` workflow depends on transactions, so it'll also fail until the host is configured for it.
|
|
211
|
+
|
|
212
|
+
## tmdate shape (`dotw` map)
|
|
213
|
+
|
|
214
|
+
The OpenAPI spec describes `tmdate` as a `string` field with `x-go-name: "JsonDefinition"`. The actual runtime shape is a structured object:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{"dotw": {
|
|
218
|
+
"mon": [{"from":"09:00","to":"17:00"}],
|
|
219
|
+
"tue": [{"from":"09:00","to":"17:00"}],
|
|
220
|
+
...
|
|
221
|
+
"sat": [],
|
|
222
|
+
"sun": []
|
|
223
|
+
}}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
A day with an empty list is inactive. A day can have multiple windows (e.g. split shift). The CLI's `--from/--to/--days` short flags build the map for you; for anything more complex use `--file` with the full JSON.
|
|
227
|
+
|
|
182
228
|
## Path-version policy (CLI internal)
|
|
183
229
|
|
|
184
230
|
Where the spec offers both legacy `/api/pbul/{hostid}/rbp/<x>` and newer
|
|
@@ -323,12 +323,20 @@ class EPMLClient:
|
|
|
323
323
|
return self.get(f"/api/pbul/{h}/rbp/cmdgrps/{cmdgrp_id}/commands")
|
|
324
324
|
|
|
325
325
|
def add_cmdgrp_commands(self, cmdgrp_id: int, commands: List[str], host_id: Optional[int] = None) -> Any:
|
|
326
|
+
"""Append commands to a cmdgrp. POST is additive, not replacing."""
|
|
326
327
|
h = self.host(host_id)
|
|
327
|
-
|
|
328
|
+
body = [{"cmd": c} for c in commands]
|
|
329
|
+
return self.post(f"/api/pbul/{h}/rbp/cmdgrps/{cmdgrp_id}/commands", json=body)
|
|
328
330
|
|
|
329
|
-
def
|
|
331
|
+
def clear_cmdgrp_commands(self, cmdgrp_id: int, host_id: Optional[int] = None) -> None:
|
|
332
|
+
"""DELETE all commands in a cmdgrp. The API has no per-item delete."""
|
|
330
333
|
h = self.host(host_id)
|
|
331
|
-
|
|
334
|
+
self._delete_no_body(f"/api/pbul/{h}/rbp/cmdgrps/{cmdgrp_id}/commands")
|
|
335
|
+
|
|
336
|
+
def replace_cmdgrp_commands(self, cmdgrp_id: int, commands: List[str], host_id: Optional[int] = None) -> Any:
|
|
337
|
+
"""Atomic-ish replace: clear then add. Two requests; not server-atomic."""
|
|
338
|
+
self.clear_cmdgrp_commands(cmdgrp_id, host_id=host_id)
|
|
339
|
+
return self.add_cmdgrp_commands(cmdgrp_id, commands, host_id=host_id)
|
|
332
340
|
|
|
333
341
|
# ----- RBP: host groups ---------------------------------------------
|
|
334
342
|
|
|
@@ -353,12 +361,19 @@ class EPMLClient:
|
|
|
353
361
|
return self.get(f"/api/pbul/{h}/rbp/hostgrps/{hostgrp_id}/hosts")
|
|
354
362
|
|
|
355
363
|
def add_hostgrp_hosts(self, hostgrp_id: int, hosts: List[str], host_id: Optional[int] = None) -> Any:
|
|
364
|
+
"""Append hostnames/patterns to a hostgrp. POST is additive."""
|
|
356
365
|
h = self.host(host_id)
|
|
357
|
-
|
|
366
|
+
body = [{"host": x} for x in hosts]
|
|
367
|
+
return self.post(f"/api/pbul/{h}/rbp/hostgrps/{hostgrp_id}/hosts", json=body)
|
|
358
368
|
|
|
359
|
-
def
|
|
369
|
+
def clear_hostgrp_hosts(self, hostgrp_id: int, host_id: Optional[int] = None) -> None:
|
|
370
|
+
"""DELETE all hosts in a hostgrp. The API has no per-item delete."""
|
|
360
371
|
h = self.host(host_id)
|
|
361
|
-
|
|
372
|
+
self._delete_no_body(f"/api/pbul/{h}/rbp/hostgrps/{hostgrp_id}/hosts")
|
|
373
|
+
|
|
374
|
+
def replace_hostgrp_hosts(self, hostgrp_id: int, hosts: List[str], host_id: Optional[int] = None) -> Any:
|
|
375
|
+
self.clear_hostgrp_hosts(hostgrp_id, host_id=host_id)
|
|
376
|
+
return self.add_hostgrp_hosts(hostgrp_id, hosts, host_id=host_id)
|
|
362
377
|
|
|
363
378
|
# ----- RBP: user groups ---------------------------------------------
|
|
364
379
|
|
|
@@ -383,12 +398,35 @@ class EPMLClient:
|
|
|
383
398
|
return self.get(f"/api/pbul/{h}/rbp/usergrps/{usergrp_id}/users")
|
|
384
399
|
|
|
385
400
|
def add_usergrp_users(self, usergrp_id: int, users: List[str], host_id: Optional[int] = None) -> Any:
|
|
401
|
+
"""Append usernames to a usergrp. POST is additive."""
|
|
386
402
|
h = self.host(host_id)
|
|
387
|
-
|
|
403
|
+
body = [{"user": u} for u in users]
|
|
404
|
+
return self.post(f"/api/pbul/{h}/rbp/usergrps/{usergrp_id}/users", json=body)
|
|
405
|
+
|
|
406
|
+
def clear_usergrp_users(self, usergrp_id: int, host_id: Optional[int] = None) -> None:
|
|
407
|
+
"""DELETE all users in a usergrp. The API has no per-item delete."""
|
|
408
|
+
h = self.host(host_id)
|
|
409
|
+
self._delete_no_body(f"/api/pbul/{h}/rbp/usergrps/{usergrp_id}/users")
|
|
410
|
+
|
|
411
|
+
def replace_usergrp_users(self, usergrp_id: int, users: List[str], host_id: Optional[int] = None) -> Any:
|
|
412
|
+
self.clear_usergrp_users(usergrp_id, host_id=host_id)
|
|
413
|
+
return self.add_usergrp_users(usergrp_id, users, host_id=host_id)
|
|
388
414
|
|
|
389
|
-
def
|
|
415
|
+
def create_multiple_usergrps(self, groups: List[Dict[str, Any]], host_id: Optional[int] = None) -> Any:
|
|
416
|
+
"""Bulk create user groups via /usergrps/multiple.
|
|
417
|
+
|
|
418
|
+
Each group dict needs at least `name` and `type` (`I` or `E`).
|
|
419
|
+
Body is wrapped: `{"usergroups": [...]}` — the spec doesn't document
|
|
420
|
+
the wrapper key.
|
|
421
|
+
"""
|
|
390
422
|
h = self.host(host_id)
|
|
391
|
-
|
|
423
|
+
# Default any missing types to "I"
|
|
424
|
+
normalized = []
|
|
425
|
+
for g in groups:
|
|
426
|
+
entry = dict(g)
|
|
427
|
+
entry.setdefault("type", "I")
|
|
428
|
+
normalized.append(entry)
|
|
429
|
+
return self.post(f"/api/pbul/{h}/rbp/usergrps/multiple", json={"usergroups": normalized})
|
|
392
430
|
|
|
393
431
|
# ----- RBP: time/date groups ----------------------------------------
|
|
394
432
|
|
|
@@ -405,6 +443,26 @@ class EPMLClient:
|
|
|
405
443
|
for tid in ids:
|
|
406
444
|
self._delete_with_query(f"/api/pbul/{h}/rbp/tmdategrps", {"id": tid})
|
|
407
445
|
|
|
446
|
+
# --- tmdates (children of tmdategrps) ---
|
|
447
|
+
|
|
448
|
+
def list_tmdategrp_tmdates(self, tmdategrp_id: int, host_id: Optional[int] = None) -> Any:
|
|
449
|
+
h = self.host(host_id)
|
|
450
|
+
return self.get(f"/api/pbul/{h}/rbp/tmdategrps/{tmdategrp_id}/tmdates")
|
|
451
|
+
|
|
452
|
+
def add_tmdategrp_tmdates(self, tmdategrp_id: int, tmdates: List[Dict[str, Any]], host_id: Optional[int] = None) -> Any:
|
|
453
|
+
"""Append time/date entries to a tmdategrp. Body is wrapped: `{tmdates: [...]}`."""
|
|
454
|
+
h = self.host(host_id)
|
|
455
|
+
return self.post(f"/api/pbul/{h}/rbp/tmdategrps/{tmdategrp_id}/tmdates", json={"tmdates": tmdates})
|
|
456
|
+
|
|
457
|
+
def clear_tmdategrp_tmdates(self, tmdategrp_id: int, host_id: Optional[int] = None) -> None:
|
|
458
|
+
"""DELETE all tmdates in a tmdategrp."""
|
|
459
|
+
h = self.host(host_id)
|
|
460
|
+
self._delete_no_body(f"/api/pbul/{h}/rbp/tmdategrps/{tmdategrp_id}/tmdates")
|
|
461
|
+
|
|
462
|
+
def replace_tmdategrp_tmdates(self, tmdategrp_id: int, tmdates: List[Dict[str, Any]], host_id: Optional[int] = None) -> Any:
|
|
463
|
+
self.clear_tmdategrp_tmdates(tmdategrp_id, host_id=host_id)
|
|
464
|
+
return self.add_tmdategrp_tmdates(tmdategrp_id, tmdates, host_id=host_id)
|
|
465
|
+
|
|
408
466
|
# ----- RBP: roles ---------------------------------------------------
|
|
409
467
|
|
|
410
468
|
def list_roles(self, host_id: Optional[int] = None) -> Any:
|
|
@@ -582,6 +640,18 @@ class EPMLClient:
|
|
|
582
640
|
except ValueError:
|
|
583
641
|
return response.text
|
|
584
642
|
|
|
643
|
+
def _delete_no_body(self, spec_path: str) -> None:
|
|
644
|
+
"""DELETE with no params and no body.
|
|
645
|
+
|
|
646
|
+
RBP child collections (e.g. cmdgrps/{id}/commands, hostgrps/{id}/hosts,
|
|
647
|
+
usergrps/{id}/users, tmdategrps/{id}/tmdates) all behave as
|
|
648
|
+
"delete all" when this is called — there's no per-item delete in the
|
|
649
|
+
v1 API. To remove a single child, GET the list, clear, then re-add the
|
|
650
|
+
ones you want to keep.
|
|
651
|
+
"""
|
|
652
|
+
response = self._client.delete(self._build_url(spec_path), headers=self._headers())
|
|
653
|
+
response.raise_for_status()
|
|
654
|
+
|
|
585
655
|
def _delete_with_query(self, spec_path: str, params: Dict[str, Any]) -> None:
|
|
586
656
|
"""DELETE with query parameters (no body).
|
|
587
657
|
|
|
@@ -96,7 +96,7 @@ def list_commands(
|
|
|
96
96
|
print_json(data)
|
|
97
97
|
else:
|
|
98
98
|
rows = data if isinstance(data, list) else (data.get("data", []) if isinstance(data, dict) else [])
|
|
99
|
-
print_table(rows, [("
|
|
99
|
+
print_table(rows, [("Command", "cmd"), ("Description", "description"), ("Disabled", "disabled")], title=f"Commands in cmdgrp {cmdgrp_id}")
|
|
100
100
|
except httpx.HTTPStatusError as e:
|
|
101
101
|
print_api_error(e, "list commands"); raise typer.Exit(1)
|
|
102
102
|
except Exception as e:
|
|
@@ -122,20 +122,44 @@ def add_commands(
|
|
|
122
122
|
print_api_error(e, "add commands"); raise typer.Exit(1)
|
|
123
123
|
|
|
124
124
|
|
|
125
|
-
@commands_app.command("
|
|
126
|
-
def
|
|
125
|
+
@commands_app.command("clear")
|
|
126
|
+
def clear_commands(
|
|
127
127
|
cmdgrp_id: int = typer.Argument(..., help="Command group ID"),
|
|
128
|
-
ids: str = typer.Option(..., "--ids", help="Comma-separated command IDs"),
|
|
129
128
|
host: Optional[int] = _host_opt(),
|
|
129
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
130
130
|
):
|
|
131
|
-
"""Remove commands from a command group.
|
|
131
|
+
"""Remove ALL commands from a command group.
|
|
132
|
+
|
|
133
|
+
The API has no per-item delete — DELETE on the children collection clears
|
|
134
|
+
everything. To selectively remove, use `replace`.
|
|
135
|
+
"""
|
|
132
136
|
from bt_cli.epml.client import get_client
|
|
137
|
+
if not yes:
|
|
138
|
+
typer.confirm(f"Clear all commands in cmdgrp {cmdgrp_id}?", abort=True)
|
|
133
139
|
try:
|
|
134
|
-
id_list = [int(x.strip()) for x in ids.split(",") if x.strip()]
|
|
135
140
|
with get_client() as c:
|
|
136
|
-
c.
|
|
137
|
-
typer.echo(f"
|
|
141
|
+
c.clear_cmdgrp_commands(cmdgrp_id, host_id=host)
|
|
142
|
+
typer.echo(f"Cleared all commands from cmdgrp {cmdgrp_id}")
|
|
143
|
+
except httpx.HTTPStatusError as e:
|
|
144
|
+
print_api_error(e, "clear commands"); raise typer.Exit(1)
|
|
145
|
+
except Exception as e:
|
|
146
|
+
print_api_error(e, "clear commands"); raise typer.Exit(1)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@commands_app.command("replace")
|
|
150
|
+
def replace_commands(
|
|
151
|
+
cmdgrp_id: int = typer.Argument(..., help="Command group ID"),
|
|
152
|
+
commands: str = typer.Option(..., "--commands", "-c", help="Comma-separated command strings — final list"),
|
|
153
|
+
host: Optional[int] = _host_opt(),
|
|
154
|
+
):
|
|
155
|
+
"""Replace the entire command list (clear + add). Two requests; not server-atomic."""
|
|
156
|
+
from bt_cli.epml.client import get_client
|
|
157
|
+
try:
|
|
158
|
+
cmds = [x.strip() for x in commands.split(",") if x.strip()]
|
|
159
|
+
with get_client() as c:
|
|
160
|
+
result = c.replace_cmdgrp_commands(cmdgrp_id, cmds, host_id=host)
|
|
161
|
+
print_json(result)
|
|
138
162
|
except httpx.HTTPStatusError as e:
|
|
139
|
-
print_api_error(e, "
|
|
163
|
+
print_api_error(e, "replace commands"); raise typer.Exit(1)
|
|
140
164
|
except Exception as e:
|
|
141
|
-
print_api_error(e, "
|
|
165
|
+
print_api_error(e, "replace commands"); raise typer.Exit(1)
|
|
@@ -100,7 +100,7 @@ def list_hosts(
|
|
|
100
100
|
print_json(data)
|
|
101
101
|
else:
|
|
102
102
|
rows = data if isinstance(data, list) else (data.get("data", []) if isinstance(data, dict) else [])
|
|
103
|
-
print_table(rows, [("
|
|
103
|
+
print_table(rows, [("Host", "host"), ("Description", "description"), ("Disabled", "disabled")], title=f"Hosts in hostgrp {hostgrp_id}")
|
|
104
104
|
except httpx.HTTPStatusError as e:
|
|
105
105
|
print_api_error(e, "list hostgrp hosts"); raise typer.Exit(1)
|
|
106
106
|
except Exception as e:
|
|
@@ -126,20 +126,44 @@ def add_hosts(
|
|
|
126
126
|
print_api_error(e, "add hostgrp hosts"); raise typer.Exit(1)
|
|
127
127
|
|
|
128
128
|
|
|
129
|
-
@hosts_app.command("
|
|
130
|
-
def
|
|
129
|
+
@hosts_app.command("clear")
|
|
130
|
+
def clear_hosts(
|
|
131
131
|
hostgrp_id: int = typer.Argument(..., help="Host group ID"),
|
|
132
|
-
ids: str = typer.Option(..., "--ids", help="Comma-separated host IDs"),
|
|
133
132
|
host: Optional[int] = _host_opt(),
|
|
133
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
134
134
|
):
|
|
135
|
-
"""Remove hosts from a host group.
|
|
135
|
+
"""Remove ALL hosts from a host group.
|
|
136
|
+
|
|
137
|
+
The API has no per-item delete — DELETE clears everything. Use `replace`
|
|
138
|
+
for selective removal.
|
|
139
|
+
"""
|
|
136
140
|
from bt_cli.epml.client import get_client
|
|
141
|
+
if not yes:
|
|
142
|
+
typer.confirm(f"Clear all hosts in hostgrp {hostgrp_id}?", abort=True)
|
|
137
143
|
try:
|
|
138
|
-
id_list = [int(x.strip()) for x in ids.split(",") if x.strip()]
|
|
139
144
|
with get_client() as c:
|
|
140
|
-
c.
|
|
141
|
-
typer.echo(f"
|
|
145
|
+
c.clear_hostgrp_hosts(hostgrp_id, host_id=host)
|
|
146
|
+
typer.echo(f"Cleared all hosts from hostgrp {hostgrp_id}")
|
|
147
|
+
except httpx.HTTPStatusError as e:
|
|
148
|
+
print_api_error(e, "clear hostgrp hosts"); raise typer.Exit(1)
|
|
149
|
+
except Exception as e:
|
|
150
|
+
print_api_error(e, "clear hostgrp hosts"); raise typer.Exit(1)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@hosts_app.command("replace")
|
|
154
|
+
def replace_hosts(
|
|
155
|
+
hostgrp_id: int = typer.Argument(..., help="Host group ID"),
|
|
156
|
+
hosts: str = typer.Option(..., "--hosts", help="Comma-separated host name/glob expressions — final list"),
|
|
157
|
+
host: Optional[int] = _host_opt(),
|
|
158
|
+
):
|
|
159
|
+
"""Replace the entire host list (clear + add). Two requests; not server-atomic."""
|
|
160
|
+
from bt_cli.epml.client import get_client
|
|
161
|
+
try:
|
|
162
|
+
host_list = [x.strip() for x in hosts.split(",") if x.strip()]
|
|
163
|
+
with get_client() as c:
|
|
164
|
+
result = c.replace_hostgrp_hosts(hostgrp_id, host_list, host_id=host)
|
|
165
|
+
print_json(result)
|
|
142
166
|
except httpx.HTTPStatusError as e:
|
|
143
|
-
print_api_error(e, "
|
|
167
|
+
print_api_error(e, "replace hostgrp hosts"); raise typer.Exit(1)
|
|
144
168
|
except Exception as e:
|
|
145
|
-
print_api_error(e, "
|
|
169
|
+
print_api_error(e, "replace hostgrp hosts"); raise typer.Exit(1)
|