bt-cli 0.4.33__tar.gz → 0.4.35__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.33/src/bt_cli/data → bt_cli-0.4.35/.claude}/skills/epml/SKILL.md +32 -6
- {bt_cli-0.4.33 → bt_cli-0.4.35}/CLAUDE.md +1 -1
- {bt_cli-0.4.33 → bt_cli-0.4.35}/PKG-INFO +1 -1
- {bt_cli-0.4.33 → bt_cli-0.4.35}/pyproject.toml +1 -1
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/__init__.py +1 -1
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/cli.py +15 -7
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/data/CLAUDE.md +1 -1
- {bt_cli-0.4.33/.claude → bt_cli-0.4.35/src/bt_cli/data}/skills/epml/SKILL.md +32 -6
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/client/base.py +120 -23
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_cmdgrps.py +34 -10
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_hostgrps.py +40 -12
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_roles.py +10 -2
- bt_cli-0.4.35/src/bt_cli/epml/commands/rbp_tmdategrps.py +198 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_usergrps.py +73 -12
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/epml/test_client.py +7 -4
- bt_cli-0.4.33/src/bt_cli/epml/commands/rbp_tmdategrps.py +0 -77
- {bt_cli-0.4.33/src/bt_cli/data → bt_cli-0.4.35/.claude}/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.33/src/bt_cli/data → bt_cli-0.4.35/.claude}/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.33/src/bt_cli/data → bt_cli-0.4.35/.claude}/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.33/src/bt_cli/data → bt_cli-0.4.35/.claude}/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.33/src/bt_cli/data → bt_cli-0.4.35/.claude}/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/.env.example +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/.github/workflows/ci.yml +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/.github/workflows/release.yml +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/.gitignore +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/README.md +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/assets/cli-help.png +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/assets/cli-output.png +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/bt-cli.spec +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/bt_entry.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/epml-implementation-plan.md +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/scripts/bt_entry.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/scripts/sync-package-data.sh +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/commands/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/commands/configure.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/commands/learn.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/commands/quick.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/core/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/core/auth.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/core/client.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/core/config.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/core/config_file.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/core/csv_utils.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/core/errors.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/core/output.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/core/prompts.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/core/rest_debug.py +0 -0
- {bt_cli-0.4.33/tests/pws → bt_cli-0.4.35/src/bt_cli/data}/__init__.py +0 -0
- {bt_cli-0.4.33/.claude → bt_cli-0.4.35/src/bt_cli/data}/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.33/.claude → bt_cli-0.4.35/src/bt_cli/data}/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.33/.claude → bt_cli-0.4.35/src/bt_cli/data}/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.33/.claude → bt_cli-0.4.35/src/bt_cli/data}/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.33/.claude → bt_cli-0.4.35/src/bt_cli/data}/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/client/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/client/base.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/accounts.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/applications.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/auth.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/bundles.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/integrations.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/permissions.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/policies.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/resources.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/roles.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/users.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/workflows.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/models/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/models/bundle.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/models/common.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/models/integration.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/models/permission.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/models/policy.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/models/resource.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/models/role.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/models/user.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/entitle/models/workflow.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/client/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/audit.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/auth.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/client_pkg.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/external_apis.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/hosts.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/iolog.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/license.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/quick.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/settings.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/siems.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/commands/users.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epml/models/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/client/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/client/base.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/audits.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/auth.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/computers.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/events.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/groups.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/policies.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/quick.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/requests.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/roles.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/tasks.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/users.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/epmw/models/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/client/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/client/base.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/commands/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/commands/auth.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/commands/import_export.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/commands/jump_clients.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/commands/jump_groups.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/commands/jump_items.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/commands/jumpoints.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/commands/policies.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/commands/quick.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/commands/teams.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/commands/users.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/commands/vault.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/models/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/models/common.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/models/jump_client.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/models/jump_group.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/models/jump_item.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/models/jumpoint.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/models/team.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/models/user.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pra/models/vault.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/client/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/client/base.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/client/beyondinsight.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/client/passwordsafe.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/accounts.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/assets.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/attributes.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/auth.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/clouds.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/config.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/credentials.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/databases.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/directories.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/functional.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/import_export.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/platforms.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/quick.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/search.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/secrets.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/systems.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/users.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/commands/workgroups.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/config.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/models/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/models/account.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/models/asset.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/models/common.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/src/bt_cli/pws/models/system.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/conftest.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/core/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/core/test_auth.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/core/test_config.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/core/test_errors.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/core/test_rest_debug.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/entitle/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/entitle/test_client.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/entitle/test_commands.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/entitle-smoke-test.sh +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/epml/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/epml/test_commands.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/epmw/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/epmw/test_client.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/epmw/test_commands.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/epmw-quick-test-plan.md +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/fixtures/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/fixtures/responses.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/integration/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/integration/conftest.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/integration/helpers.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/integration/test_entitle_integration.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/integration/test_epmw_integration.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/integration/test_epmw_lifecycle.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/integration/test_pra_integration.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/integration/test_pra_lifecycle.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/integration/test_pws_integration.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/integration/test_pws_lifecycle.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/pra/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/pra/test_client.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/pra/test_commands.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/pra-smoke-test.sh +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/pra-test-plan.md +0 -0
- {bt_cli-0.4.33/src/bt_cli/data → bt_cli-0.4.35/tests/pws}/__init__.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/pws/test_client.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/pws/test_commands.py +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/tests/pws-quick-test-plan.md +0 -0
- {bt_cli-0.4.33 → bt_cli-0.4.35}/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,21 @@ 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
|
+
|
|
182
208
|
## Path-version policy (CLI internal)
|
|
183
209
|
|
|
184
210
|
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,21 @@ 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
|
+
|
|
182
208
|
## Path-version policy (CLI internal)
|
|
183
209
|
|
|
184
210
|
Where the spec offers both legacy `/api/pbul/{hostid}/rbp/<x>` and newer
|
|
@@ -313,22 +313,30 @@ class EPMLClient:
|
|
|
313
313
|
h = self.host(host_id)
|
|
314
314
|
return self.post(f"/api/pbul/{h}/rbp/cmdgrps", json=data)
|
|
315
315
|
|
|
316
|
-
def delete_cmdgrps(self, ids: List[int], host_id: Optional[int] = None) ->
|
|
316
|
+
def delete_cmdgrps(self, ids: List[int], host_id: Optional[int] = None) -> None:
|
|
317
317
|
h = self.host(host_id)
|
|
318
|
-
|
|
319
|
-
|
|
318
|
+
for cid in ids:
|
|
319
|
+
self._delete_with_query(f"/api/pbul/{h}/rbp/cmdgrps", {"id": cid})
|
|
320
320
|
|
|
321
321
|
def list_cmdgrp_commands(self, cmdgrp_id: int, host_id: Optional[int] = None) -> Any:
|
|
322
322
|
h = self.host(host_id)
|
|
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
|
|
|
@@ -338,23 +346,34 @@ class EPMLClient:
|
|
|
338
346
|
|
|
339
347
|
def create_hostgrp(self, data: Dict[str, Any], host_id: Optional[int] = None) -> Any:
|
|
340
348
|
h = self.host(host_id)
|
|
341
|
-
|
|
349
|
+
# Server requires `type` ∈ {"I","E"}. Default to Internal if omitted.
|
|
350
|
+
body = dict(data)
|
|
351
|
+
body.setdefault("type", "I")
|
|
352
|
+
return self.post(f"/api/pbul/{h}/rbp/hostgrps", json=body)
|
|
342
353
|
|
|
343
|
-
def delete_hostgrps(self, ids: List[int], host_id: Optional[int] = None) ->
|
|
354
|
+
def delete_hostgrps(self, ids: List[int], host_id: Optional[int] = None) -> None:
|
|
344
355
|
h = self.host(host_id)
|
|
345
|
-
|
|
356
|
+
for hid in ids:
|
|
357
|
+
self._delete_with_query(f"/api/pbul/{h}/rbp/hostgrps", {"id": hid})
|
|
346
358
|
|
|
347
359
|
def list_hostgrp_hosts(self, hostgrp_id: int, host_id: Optional[int] = None) -> Any:
|
|
348
360
|
h = self.host(host_id)
|
|
349
361
|
return self.get(f"/api/pbul/{h}/rbp/hostgrps/{hostgrp_id}/hosts")
|
|
350
362
|
|
|
351
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."""
|
|
352
365
|
h = self.host(host_id)
|
|
353
|
-
|
|
366
|
+
body = [{"host": x} for x in hosts]
|
|
367
|
+
return self.post(f"/api/pbul/{h}/rbp/hostgrps/{hostgrp_id}/hosts", json=body)
|
|
354
368
|
|
|
355
|
-
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."""
|
|
356
371
|
h = self.host(host_id)
|
|
357
|
-
|
|
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)
|
|
358
377
|
|
|
359
378
|
# ----- RBP: user groups ---------------------------------------------
|
|
360
379
|
|
|
@@ -364,23 +383,50 @@ class EPMLClient:
|
|
|
364
383
|
|
|
365
384
|
def create_usergrp(self, data: Dict[str, Any], host_id: Optional[int] = None) -> Any:
|
|
366
385
|
h = self.host(host_id)
|
|
367
|
-
|
|
386
|
+
# Server requires `type` ∈ {"I","E"}. Default to Internal if omitted.
|
|
387
|
+
body = dict(data)
|
|
388
|
+
body.setdefault("type", "I")
|
|
389
|
+
return self.post(f"/api/pbul/{h}/rbp/usergrps", json=body)
|
|
368
390
|
|
|
369
|
-
def delete_usergrps(self, ids: List[int], host_id: Optional[int] = None) ->
|
|
391
|
+
def delete_usergrps(self, ids: List[int], host_id: Optional[int] = None) -> None:
|
|
370
392
|
h = self.host(host_id)
|
|
371
|
-
|
|
393
|
+
for uid in ids:
|
|
394
|
+
self._delete_with_query(f"/api/pbul/{h}/rbp/usergrps", {"id": uid})
|
|
372
395
|
|
|
373
396
|
def list_usergrp_users(self, usergrp_id: int, host_id: Optional[int] = None) -> Any:
|
|
374
397
|
h = self.host(host_id)
|
|
375
398
|
return self.get(f"/api/pbul/{h}/rbp/usergrps/{usergrp_id}/users")
|
|
376
399
|
|
|
377
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."""
|
|
402
|
+
h = self.host(host_id)
|
|
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."""
|
|
378
408
|
h = self.host(host_id)
|
|
379
|
-
|
|
409
|
+
self._delete_no_body(f"/api/pbul/{h}/rbp/usergrps/{usergrp_id}/users")
|
|
380
410
|
|
|
381
|
-
def
|
|
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)
|
|
414
|
+
|
|
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
|
+
"""
|
|
382
422
|
h = self.host(host_id)
|
|
383
|
-
|
|
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})
|
|
384
430
|
|
|
385
431
|
# ----- RBP: time/date groups ----------------------------------------
|
|
386
432
|
|
|
@@ -392,9 +438,30 @@ class EPMLClient:
|
|
|
392
438
|
h = self.host(host_id)
|
|
393
439
|
return self.post(f"/api/pbul/{h}/rbp/tmdategrps", json=data)
|
|
394
440
|
|
|
395
|
-
def delete_tmdategrps(self, ids: List[int], host_id: Optional[int] = None) ->
|
|
441
|
+
def delete_tmdategrps(self, ids: List[int], host_id: Optional[int] = None) -> None:
|
|
442
|
+
h = self.host(host_id)
|
|
443
|
+
for tid in ids:
|
|
444
|
+
self._delete_with_query(f"/api/pbul/{h}/rbp/tmdategrps", {"id": tid})
|
|
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."""
|
|
396
459
|
h = self.host(host_id)
|
|
397
|
-
|
|
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)
|
|
398
465
|
|
|
399
466
|
# ----- RBP: roles ---------------------------------------------------
|
|
400
467
|
|
|
@@ -404,11 +471,15 @@ class EPMLClient:
|
|
|
404
471
|
|
|
405
472
|
def create_role(self, data: Dict[str, Any], host_id: Optional[int] = None) -> Any:
|
|
406
473
|
h = self.host(host_id)
|
|
407
|
-
|
|
474
|
+
# Server requires `action` ∈ {"A","R"} (Allow / Reject). Default Allow.
|
|
475
|
+
body = dict(data)
|
|
476
|
+
body.setdefault("action", "A")
|
|
477
|
+
return self.post(f"/api/pbul/{h}/rbp/roles", json=body)
|
|
408
478
|
|
|
409
|
-
def delete_roles(self, ids: List[int], host_id: Optional[int] = None) ->
|
|
479
|
+
def delete_roles(self, ids: List[int], host_id: Optional[int] = None) -> None:
|
|
410
480
|
h = self.host(host_id)
|
|
411
|
-
|
|
481
|
+
for rid in ids:
|
|
482
|
+
self._delete_with_query(f"/api/pbul/{h}/rbp/roles", {"id": rid})
|
|
412
483
|
|
|
413
484
|
def duplicate_role(self, role_id: int, host_id: Optional[int] = None) -> Any:
|
|
414
485
|
h = self.host(host_id)
|
|
@@ -569,6 +640,32 @@ class EPMLClient:
|
|
|
569
640
|
except ValueError:
|
|
570
641
|
return response.text
|
|
571
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
|
+
|
|
655
|
+
def _delete_with_query(self, spec_path: str, params: Dict[str, Any]) -> None:
|
|
656
|
+
"""DELETE with query parameters (no body).
|
|
657
|
+
|
|
658
|
+
RBP collection deletes (`/pbul/{hostid}/rbp/{entity}`) take `id` as a
|
|
659
|
+
query param, not a JSON body. Repeated `?id=A&id=B` is accepted by the
|
|
660
|
+
server but only the first value is honored — callers should loop.
|
|
661
|
+
"""
|
|
662
|
+
response = self._client.delete(
|
|
663
|
+
self._build_url(spec_path),
|
|
664
|
+
headers=self._headers(),
|
|
665
|
+
params=params,
|
|
666
|
+
)
|
|
667
|
+
response.raise_for_status()
|
|
668
|
+
|
|
572
669
|
# ----- iologs --------------------------------------------------------
|
|
573
670
|
|
|
574
671
|
def list_iologs(
|
|
@@ -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)
|
|
@@ -45,13 +45,17 @@ def list_hostgrps(
|
|
|
45
45
|
def create_hostgrp(
|
|
46
46
|
name: str = typer.Option(..., "--name", "-n"),
|
|
47
47
|
description: str = typer.Option("", "--description", "-d"),
|
|
48
|
+
type_: str = typer.Option("I", "--type", "-t", help="Group type: I=Internal, E=External"),
|
|
48
49
|
host: Optional[int] = _host_opt(),
|
|
49
50
|
):
|
|
50
|
-
"""Create a host group."""
|
|
51
|
+
"""Create a host group. API requires `type` ∈ {I,E}; defaults to Internal."""
|
|
51
52
|
from bt_cli.epml.client import get_client
|
|
53
|
+
if type_ not in ("I", "E"):
|
|
54
|
+
typer.echo(f"--type must be 'I' or 'E', got {type_!r}", err=True)
|
|
55
|
+
raise typer.Exit(2)
|
|
52
56
|
try:
|
|
53
57
|
with get_client() as c:
|
|
54
|
-
result = c.create_hostgrp({"name": name, "description": description}, host_id=host)
|
|
58
|
+
result = c.create_hostgrp({"name": name, "description": description, "type": type_}, host_id=host)
|
|
55
59
|
print_json(result)
|
|
56
60
|
except httpx.HTTPStatusError as e:
|
|
57
61
|
print_api_error(e, "create hostgrp"); raise typer.Exit(1)
|
|
@@ -96,7 +100,7 @@ def list_hosts(
|
|
|
96
100
|
print_json(data)
|
|
97
101
|
else:
|
|
98
102
|
rows = data if isinstance(data, list) else (data.get("data", []) if isinstance(data, dict) else [])
|
|
99
|
-
print_table(rows, [("
|
|
103
|
+
print_table(rows, [("Host", "host"), ("Description", "description"), ("Disabled", "disabled")], title=f"Hosts in hostgrp {hostgrp_id}")
|
|
100
104
|
except httpx.HTTPStatusError as e:
|
|
101
105
|
print_api_error(e, "list hostgrp hosts"); raise typer.Exit(1)
|
|
102
106
|
except Exception as e:
|
|
@@ -122,20 +126,44 @@ def add_hosts(
|
|
|
122
126
|
print_api_error(e, "add hostgrp hosts"); raise typer.Exit(1)
|
|
123
127
|
|
|
124
128
|
|
|
125
|
-
@hosts_app.command("
|
|
126
|
-
def
|
|
129
|
+
@hosts_app.command("clear")
|
|
130
|
+
def clear_hosts(
|
|
127
131
|
hostgrp_id: int = typer.Argument(..., help="Host group ID"),
|
|
128
|
-
ids: str = typer.Option(..., "--ids", help="Comma-separated host IDs"),
|
|
129
132
|
host: Optional[int] = _host_opt(),
|
|
133
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
130
134
|
):
|
|
131
|
-
"""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
|
+
"""
|
|
132
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)
|
|
133
143
|
try:
|
|
134
|
-
id_list = [int(x.strip()) for x in ids.split(",") if x.strip()]
|
|
135
144
|
with get_client() as c:
|
|
136
|
-
c.
|
|
137
|
-
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)
|
|
138
166
|
except httpx.HTTPStatusError as e:
|
|
139
|
-
print_api_error(e, "
|
|
167
|
+
print_api_error(e, "replace hostgrp hosts"); raise typer.Exit(1)
|
|
140
168
|
except Exception as e:
|
|
141
|
-
print_api_error(e, "
|
|
169
|
+
print_api_error(e, "replace hostgrp hosts"); raise typer.Exit(1)
|
|
@@ -45,13 +45,21 @@ def list_roles(
|
|
|
45
45
|
def create_role(
|
|
46
46
|
name: str = typer.Option(..., "--name", "-n"),
|
|
47
47
|
description: str = typer.Option("", "--description", "-d"),
|
|
48
|
+
action: str = typer.Option("A", "--action", "-a", help="Role verdict: A=Allow, R=Reject"),
|
|
48
49
|
host: Optional[int] = _host_opt(),
|
|
49
50
|
):
|
|
50
|
-
"""Create a role.
|
|
51
|
+
"""Create a role.
|
|
52
|
+
|
|
53
|
+
The API requires `action` to be exactly `A` (Allow) or `R` (Reject) —
|
|
54
|
+
not 'Allow'/'Reject'. Defaults to A.
|
|
55
|
+
"""
|
|
51
56
|
from bt_cli.epml.client import get_client
|
|
57
|
+
if action not in ("A", "R"):
|
|
58
|
+
typer.echo(f"--action must be 'A' or 'R', got {action!r}", err=True)
|
|
59
|
+
raise typer.Exit(2)
|
|
52
60
|
try:
|
|
53
61
|
with get_client() as c:
|
|
54
|
-
result = c.create_role({"name": name, "description": description}, host_id=host)
|
|
62
|
+
result = c.create_role({"name": name, "description": description, "action": action}, host_id=host)
|
|
55
63
|
print_json(result)
|
|
56
64
|
except httpx.HTTPStatusError as e:
|
|
57
65
|
print_api_error(e, "create role"); raise typer.Exit(1)
|