Skip to content

feat: add --idp-configuration-id flag for multi-IDP Tableau Cloud sites - #444

Open
jacalata wants to merge 1 commit into
developmentfrom
jac/393-multi-idp-support
Open

feat: add --idp-configuration-id flag for multi-IDP Tableau Cloud sites#444
jacalata wants to merge 1 commit into
developmentfrom
jac/393-multi-idp-support

Conversation

@jacalata

@jacalata jacalata commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Closes #393.

Motivation

Tableau Cloud 2024.3+ supports up to 20 identity providers per site.
The REST API takes an idpConfigurationId attribute when creating
users, but neither createsiteusers nor createUsers exposed it, so
operators on multi-IDP sites had to bypass tabcmd and script the REST
API directly.

Behavior change

For users:

  • --idp-configuration-id UUID added to both createsiteusers and
    createUsers. Applies to every user in the batch.
  • argparse.add_mutually_exclusive_group enforces that --auth-type
    and --idp-configuration-id can't both be passed, matching the REST
    API's own constraint (TSC's create_req raises if both are set on a
    UserItem).
  • Passing --idp-configuration-id clears any pre-existing
    auth_setting on the UserItem (mostly to handle the case where the
    CSV column 8 supplied an auth type and the CLI flag overrides it).

Scope not covered:

  • CSV format is unchanged. Adding a 9th column here alone would create
    format divergence with tabcmd 1 and the Server web-UI user import;
    per-user IDP assignment would need a coordinated cross-tool change.
  • Per-user IDP assignment via CSV is not implemented; batch-level only.

Refactor bundled: the per-user --role/--auth-type/
--idp-configuration-id application was duplicated between the two
commands. Lifted into UserCommand.apply_cli_overrides.

Test plan

  • pytest tests/ — 347 passed, 2 skipped
  • 6 new tests: parser + mutual-exclusion on each of createsiteusers
    and createUsers (4 tests); apply_cli_overrides IDP-clears-auth and
    role-application (2 tests)
  • check-strings passes; black --check clean; mypy clean
  • tabcmd createsiteusers --auth-type SAML --idp-configuration-id x users.csv — argparse rejects with "not allowed with argument
    --auth-type"

Follow-ups

Per-user IDP assignment via a 9th CSV column would need to land
coordinated with tabcmd 1 and the Server web-UI import.

🤖 Generated with Claude Code

Tableau Cloud 2024.3+ supports up to 20 identity providers per site, and
the REST API takes an idpConfigurationId attribute when creating users.
Neither `createsiteusers` nor `createUsers` previously exposed the field,
so operators on multi-IDP sites had to bypass tabcmd and script the REST
API directly.

Adds `--idp-configuration-id UUID` to both commands. The flag applies to
every user in the batch. Argparse's add_mutually_exclusive_group enforces
that --auth-type and --idp-configuration-id cannot both be passed (TSC's
create_req rejects that combination on the wire).

The CSV format is unchanged (still 8 columns) so it stays consistent with
Tableau Server's web-UI user-import and tabcmd 1. Per-user IDP assignment
via a 9th CSV column was considered and deferred; it would require a
coordinated cross-tool format update.

While here, extracts the per-user `--role`/`--auth-type`/`--idp-configuration-id`
application into UserCommand.apply_cli_overrides, removing the block
duplicated between the two commands, and drops an unused
tableauserverclient import from create_users_command.py.

Adds 6 new tests: 4 for the parser (both commands, valid parse + mutual
exclusion), 2 for apply_cli_overrides (IDP clears pre-existing
auth_setting, role flag applied). All 347 tests pass.

Closes #393

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Coverage

Coverage Report
FileStmtsMissCoverMissing
tabcmd
   __main__.py121212 0%
   _version.py111111 0%
   tabcmd.py151515 0%
   version.py955 44%
tabcmd/commands
   commands.py101010 0%
   constants.py772424 69%
   server.py1351818 87%
tabcmd/commands/auth
   session.py3945050 87%
tabcmd/commands/datasources_and_workbooks
   datasources_and_workbooks_command.py1571818 89%
   datasources_workbooks_views_url_parser.py14255 96%
   delete_command.py601616 73%
   export_command.py1202525 79%
   get_url_command.py1274747 63%
   publish_command.py1232828 77%
   runschedule_command.py2177 67%
tabcmd/commands/extracts
   create_extracts_command.py4288 81%
   decrypt_extracts_command.py2722 93%
   delete_extracts_command.py3766 84%
   encrypt_extracts_command.py2722 93%
   extracts.py2022 90%
   reencrypt_extracts_command.py2722 93%
   refresh_extracts_command.py481010 79%
tabcmd/commands/group
   create_group_command.py2955 83%
   delete_group_command.py2722 93%
tabcmd/commands/project
   create_project_command.py4688 83%
   delete_project_command.py3544 89%
   publish_samples_command.py3044 87%
tabcmd/commands/site
   create_site_command.py3455 85%
   delete_site_command.py2722 93%
   edit_site_command.py3822 95%
   list_command.py771212 84%
   list_sites_command.py2922 93%
tabcmd/commands/user
   add_users_command.py2955 83%
   create_site_users.py551111 80%
   create_users_command.py551010 82%
   delete_site_users_command.py4355 88%
   user_data.py2333131 87%
tabcmd/execution
   _version.py222 0%
   global_options.py12588 94%
   localize.py661111 83%
   logger_config.py6066 90%
   tabcmd_controller.py4277 83%
TOTAL287746584% 

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for multi-IDP Tableau Cloud sites by exposing the REST API’s idpConfigurationId field via a new CLI flag, and centralizes per-user CLI override logic to avoid duplication across user-import commands.

Changes:

  • Adds --idp-configuration-id to both createsiteusers and createUsers, enforced as mutually exclusive with --auth-type.
  • Refactors role/auth/idp application into UserCommand.apply_cli_overrides and updates both commands to use it.
  • Extends parser and command-level unit tests, plus adds an i18n help string for the new flag.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/parsers/test_parser_create_user.py Adds parser coverage for --idp-configuration-id and mutual exclusion.
tests/parsers/test_parser_create_site_users.py Adds parser coverage for --idp-configuration-id and mutual exclusion.
tests/commands/test_user_utils.py Adds unit tests for UserCommand.apply_cli_overrides behavior (role/auth/idp precedence).
tests/commands/test_run_commands.py Updates mocked args to include the new idp_configuration_id attribute.
tabcmd/locales/en/tabcmd_messages_en.properties Adds help text for --idp-configuration-id.
tabcmd/commands/user/user_data.py Introduces set_auth_and_idp_args and apply_cli_overrides in shared user command base.
tabcmd/commands/user/create_users_command.py Wires new args + shared override logic into createUsers.
tabcmd/commands/user/create_site_users.py Wires new args + shared override logic into createsiteusers.
Suppressed comments (2)

tests/parsers/test_parser_create_user.py:49

  • This mutual-exclusion test also uses a non-UUID placeholder value for --idp-configuration-id. Consider switching to a valid UUID so the test reflects expected real-world input.
                "--idp-configuration-id",
                "abc-123-idp",
            ]

tests/parsers/test_parser_create_site_users.py:69

  • This mutual-exclusion test also uses a non-UUID placeholder value for --idp-configuration-id. Consider switching to a valid UUID so the test reflects expected real-world input.
                "SAML",
                "--idp-configuration-id",
                "abc-123-idp",
            ]

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +131 to +135
auth_group.add_argument(
"--idp-configuration-id",
metavar="UUID",
help=_("tabcmd.user.help.idp_configuration_id"),
)
Comment on lines +33 to +37
with mock.patch("builtins.open", mock.mock_open(read_data="test")):
mock_args = [commandname, "users.csv", "--idp-configuration-id", "abc-123-idp"]
args = self.parser_under_test.parse_args(mock_args)
assert args.idp_configuration_id == "abc-123-idp", args
assert args.auth_type is None, args
Comment on lines +53 to +57
with mock.patch("builtins.open", mock.mock_open(read_data="test")):
mock_args = [commandname, "users.csv", "--idp-configuration-id", "abc-123-idp"]
args = self.parser_under_test.parse_args(mock_args)
assert args.idp_configuration_id == "abc-123-idp", args
assert args.auth_type is None, args
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants